From efa1962266a901ca35883c6f5e3d58a81b6a7b1b Mon Sep 17 00:00:00 2001 From: Matthew Moorcroft Date: Tue, 1 Sep 2026 11:11:12 +0100 Subject: [PATCH 1/7] Deterministic lineage & dependency tracking in the discover inventory (#23) --- AGENTS.md | 2 + src/flowx/models/adf_ast.py | 68 ++- src/flowx/parser/dataset_resolvers.py | 287 ++++++++++ src/flowx/parser/lineage.py | 274 ++++++++++ src/flowx/sources/adf/loader.py | 34 ++ src/flowx/sources/adf/translators/copy.py | 264 +-------- .../adf/translators/execute_pipeline.py | 10 +- tests/unit/test_adf_loader.py | 78 +++ tests/unit/test_dataset_resolvers.py | 74 +++ tests/unit/test_lineage.py | 513 ++++++++++++++++++ 10 files changed, 1352 insertions(+), 252 deletions(-) create mode 100644 src/flowx/parser/dataset_resolvers.py create mode 100644 src/flowx/parser/lineage.py create mode 100644 tests/unit/test_dataset_resolvers.py create mode 100644 tests/unit/test_lineage.py diff --git a/AGENTS.md b/AGENTS.md index e1ae8c4..3ac722f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,6 +75,8 @@ intermediates under `.work/` (pruned by `package`). | `models/dab.py` | DAB output schema types | | `parser/adf_loader.py` | Parses ADF exports, produces `metadata/inventory.json` + `metadata/profile_report.csv` | | `parser/expression_parser.py` | Translates ADF expressions (@activity, @pipeline, @variables) | +| `parser/dataset_resolvers.py` | Shared deterministic dataset-identity resolvers (schema.table / storage path), used by convert and discover | +| `parser/lineage.py` | Deterministic control + data lineage extraction (`build_lineage`) surfaced in the inventory | | `translator/engine.py` | Registry dispatch, topological sort, context threading | | `translator/activity_translators/` | One module per deterministic activity type (16 total) | | `preparer/workflow_preparer.py` | Orchestrates activity preparers | diff --git a/src/flowx/models/adf_ast.py b/src/flowx/models/adf_ast.py index a91ef30..6047935 100644 --- a/src/flowx/models/adf_ast.py +++ b/src/flowx/models/adf_ast.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from enum import Enum -from typing import Any +from typing import Any, Literal class TranslationStrategy(Enum): @@ -292,6 +292,19 @@ def get_linked_service(self, name: str | None) -> AdfLinkedService | None: return value return None + def get_pipeline(self, name: str | None) -> AdfPipeline | None: + """Case-insensitive pipeline lookup; see :meth:`get_dataset`.""" + if not name: + return None + lowered = name.lower() + ci_fallback = None + for pipeline in self.pipelines: + if pipeline.name == name: + return pipeline + if ci_fallback is None and pipeline.name.lower() == lowered: + ci_fallback = pipeline + return ci_fallback + # --------------------------------------------------------------------------- # Inventory / classification @@ -334,3 +347,56 @@ class Inventory: agentic_count: int = 0 unsupported_count: int = 0 pipeline_count: int = 0 + lineage: Lineage | None = None + + +# --------------------------------------------------------------------------- +# Lineage graphs +# --------------------------------------------------------------------------- + + +@dataclass(slots=True, kw_only=True) +class ControlEdge: + """A cross-pipeline ExecutePipeline call edge (caller -> callee).""" + + caller_pipeline: str + callee_pipeline: str + activity_name: str + wait_on_completion: bool = True + + +@dataclass(slots=True, kw_only=True) +class DataEdge: + """A dataset producer -> consumer relationship across activities/pipelines. + + Attributes: + dataset_name: Producer-side ADF dataset name (provenance only; the join + is on ``match_key``, not this name). + identity: Resolved physical identity (``schema.table`` / storage path) + when ``match_kind == "identity"``; ``None`` otherwise. + match_kind: How producer and consumer were matched: + ``"identity"`` -- both resolve to the same physical asset (high + confidence); ``"expression"`` -- both build the same normalized + path signature from parameterized expressions (structural match, + value unknown at discover time -- lower confidence). + match_key: The value the join was made on -- the resolved identity for + ``"identity"`` edges, or the normalized path signature for + ``"expression"`` edges. Lets consumers see exactly what coupled them. + """ + + dataset_name: str + identity: str | None + producer_pipeline: str + producer_activity: str + consumer_pipeline: str + consumer_activity: str + match_kind: Literal["identity", "expression"] = "identity" + match_key: str | None = None + + +@dataclass(slots=True, kw_only=True) +class Lineage: + """Deterministic lineage graphs extracted from the parsed definitions.""" + + control_edges: list[ControlEdge] = field(default_factory=list) + data_edges: list[DataEdge] = field(default_factory=list) diff --git a/src/flowx/parser/dataset_resolvers.py b/src/flowx/parser/dataset_resolvers.py new file mode 100644 index 0000000..1646430 --- /dev/null +++ b/src/flowx/parser/dataset_resolvers.py @@ -0,0 +1,287 @@ +"""Deterministic dataset-identity resolvers. + +Resolve an ADF dataset reference to its physical identity (``schema.table`` or +storage path) and backing linked service, independent of convert-time state. +Shared by convert (``copy.py``) and discover (``lineage.py``) so both phases use +one implementation instead of duplicating the resolution logic. +""" + +from __future__ import annotations + +import re +from typing import Any + +from flowx.models.adf_ast import AdfDefinitions +from flowx.models.ir import TranslationContext +from flowx.parser.expression_parser import ( + resolve_expression, + resolve_interpolated_string, + resolve_interpolated_string_for_notebook, +) + +_ACCOUNT_NAME_RE = re.compile(r"AccountName=([A-Za-z0-9]+)", re.IGNORECASE) +_DATASET_PARAM_RE = re.compile(r"^@dataset\(\)\.([A-Za-z_][A-Za-z0-9_]*)$") + + +def dataset_props(dataset_ref: Any, definitions: AdfDefinitions) -> dict[str, Any] | None: + """Return the ``properties`` dict for an input/output dataset reference.""" + dataset = definitions.datasets.get(dataset_ref.reference_name) + if not dataset: + return None + return dict(dataset.properties or {}) + + +def resolve_param_value( + raw: Any, + dataset_params: dict[str, Any], + context: TranslationContext, + *, + for_notebook: bool = False, +) -> str: + """Resolves a single ADF location field to a string.""" + if raw is None: + return "" + if isinstance(raw, dict) and raw.get("type") == "Expression": + raw = raw.get("value", "") + if isinstance(raw, (list, dict)): + return "" + if not isinstance(raw, str): + return str(raw) + text = raw + + match = _DATASET_PARAM_RE.match(text.strip()) + if match: + param_name = match.group(1) + return resolve_param_value( + dataset_params.get(param_name, ""), dataset_params, context, for_notebook=for_notebook + ) + + if "@{" in text: + if for_notebook: + return resolve_interpolated_string_for_notebook(text, context) + return resolve_interpolated_string(text, context) + + if text.startswith("@"): + result = resolve_expression(text, context) + if result is not None and result.kind in ("literal", "dab_ref"): + return result.value + return text + + return text + + +def resolve_storage_account(linked_service: Any) -> str | None: + """Tries to pull a storage account name out of a linked service, if present.""" + if linked_service is None: + return None + type_props = linked_service.properties.get("typeProperties") or linked_service.properties + + url = type_props.get("url") or "" + if isinstance(url, str) and url: + host = url.replace("https://", "").split("/", 1)[0] + host_no_port = host.split(":", 1)[0] + if "." in host_no_port: + return host_no_port.split(".", 1)[0] + + sas_uri = type_props.get("sasUri") or "" + if isinstance(sas_uri, str) and sas_uri: + host = sas_uri.split("?", 1)[0].replace("https://", "").split("/", 1)[0] + if "." in host: + return host.split(".", 1)[0] + + # Plaintext connection string (rare in az exports — usually masked). + conn_string = type_props.get("connectionString") + if isinstance(conn_string, str): + match = _ACCOUNT_NAME_RE.search(conn_string) + if match: + return match.group(1) + if isinstance(conn_string, dict): + value = conn_string.get("value", "") + match = _ACCOUNT_NAME_RE.search(value) + if match: + return match.group(1) + + # AWS — bucket name lives on the dataset, account is implicit. + # Nothing useful to return at the linked-service level for S3/GCS. + return None + + +def resolve_dataset_path(dataset_props: dict[str, Any], definitions: AdfDefinitions) -> str | None: + """Resolves a dataset's storage path using its location + linked service.""" + type_props = dataset_props.get("typeProperties") or dataset_props + location = type_props.get("location") or {} + + file_system = location.get("fileSystem") or location.get("container") or "" + folder_path = location.get("folderPath") or "" + if isinstance(file_system, dict) or isinstance(folder_path, dict): + return None # parameterised; caller handles via _resolve_path_info + + linked_service_ref = dataset_props.get("linkedServiceName") or {} + if isinstance(linked_service_ref, dict): + linked_service_name = linked_service_ref.get("referenceName", "") + else: + linked_service_name = str(linked_service_ref) + linked_service = definitions.linked_services.get(linked_service_name) if linked_service_name else None + account = resolve_storage_account(linked_service) + if not account: + return None + + return f"abfss://{file_system}@{account}.dfs.core.windows.net/{folder_path}".rstrip("/") + + +def _effective_dataset_params(dataset_ref: Any, dataset_props: dict[str, Any]) -> dict[str, Any]: + """Returns the effective parameter map for a dataset reference. + + Args: + dataset_ref: Activity-side dataset reference (carries parameter + overrides supplied at the call site). + dataset_props: Full properties dict of the referenced dataset. + + Returns: + Mapping of parameter name to resolved value: dataset declared + defaults first, then activity-side overrides win. + """ + declared = dataset_props.get("parameters") or {} + effective: dict[str, Any] = {} + for name, spec in declared.items(): + if isinstance(spec, dict) and "defaultValue" in spec: + effective[name] = spec["defaultValue"] + if dataset_ref is not None and getattr(dataset_ref, "parameters", None): + effective.update(dict(dataset_ref.parameters)) + return effective + + +def resolve_table_reference( + dataset_ref: Any, + dataset_props: dict[str, Any] | None, + context: TranslationContext, +) -> tuple[str | None, str | None]: + """Resolves the schema and table name from a dataset reference. + + Args: + dataset_ref: Activity-side dataset reference. + dataset_props: Full properties dict of the referenced dataset. + context: Translation context for expression resolution. + + Returns: + Tuple of ``(schema, table)`` strings. Either may be ``None`` + when the dataset does not carry that field. ADF parameter + expressions are resolved against the dataset reference's + effective parameter map. Handles both the nested + ``typeProperties`` shape and the ``schemaTypePropertiesSchema`` + flattened form ``az datafactory dataset show`` emits. + """ + if not dataset_props: + return None, None + type_props = dataset_props.get("typeProperties") if isinstance(dataset_props.get("typeProperties"), dict) else None + effective_params = _effective_dataset_params(dataset_ref, dataset_props) + schema_raw = _pick_dataset_field( + type_props, + dataset_props, + ("schema", "database"), + ("schemaTypePropertiesSchema", "database"), + ) + table_raw = _pick_dataset_field( + type_props, + dataset_props, + ("table", "tableName"), + ("table", "tableName"), + ) + schema = resolve_param_value(schema_raw, effective_params, context) if schema_raw is not None else None + table = resolve_param_value(table_raw, effective_params, context) if table_raw is not None else None + return (schema or None), (table or None) + + +def _pick_dataset_field( + type_props: dict[str, Any] | None, + dataset_props: dict[str, Any], + nested_keys: tuple[str, ...], + flat_keys: tuple[str, ...], +) -> Any: + """Returns the first populated dataset field across nested and flat shapes. + + Args: + type_props: ``typeProperties`` dict when present, ``None`` + when the dataset is in the az-flattened shape. + dataset_props: Top-level dataset properties dict. + nested_keys: Keys to try inside ``type_props`` (nested ADF shape). + flat_keys: Keys to try at the top level (az flattened shape). + + Returns: + The first non-empty value found. Empty strings, empty lists, + and ``None`` are skipped so column-schema artifacts like + ``schema: []`` don't shadow the actual database schema stored + under a flattened key. + """ + candidates: list[Any] = [] + if type_props is not None: + candidates.extend(type_props.get(key) for key in nested_keys) + candidates.extend(dataset_props.get(key) for key in flat_keys) + for value in candidates: + if value is None: + continue + if isinstance(value, (list, dict)) and not value: + continue + if isinstance(value, str) and not value.strip(): + continue + return value + return None + + +def resolve_dataset_linked_service_name(dataset_props: dict[str, Any] | None) -> str | None: + """Returns the linked service name a dataset references. + + Args: + dataset_props: Full properties dict of the referenced dataset. + + Returns: + Linked service name string, or ``None`` when not present. + """ + if not dataset_props: + return None + raw = dataset_props.get("linkedServiceName") or {} + if isinstance(raw, dict): + return raw.get("referenceName") or None + return str(raw) or None + + +def _is_physical(value: str) -> bool: + """Return True only when *value* is a literal (physical) identifier. + + A value is NOT physical when it still contains an unresolved marker: + - a DAB-ref placeholder ``{{`` … ``}}`` + - a leftover ADF interpolation fragment ``@{`` + - a bare ADF expression (starts with ``@`` after stripping) + """ + stripped = value.lstrip() + return not ("{{" in value or "@{" in value or stripped.startswith("@")) + + +def resolve_dataset_identity( + dataset_ref: Any, + definitions: AdfDefinitions, + context: TranslationContext | None = None, +) -> str | None: + """Deterministic physical identity for a dataset reference. + + Returns ``"schema.table"`` when a table is resolvable, else a storage path, + else ``None`` (never a guess). Used to join producers to consumers on the + same physical asset even when their ADF dataset names differ. + + Parameterized values (ADF expressions or DAB-ref placeholders) are treated + as unresolvable and return ``None`` — they must never be used as identity + keys because two unrelated pipelines sharing the same parameter name would + collide on the same placeholder string. + """ + if dataset_ref is None: + return None + ctx = context if context is not None else TranslationContext() + props = dataset_props(dataset_ref, definitions) + if props is None: + return None + schema, table = resolve_table_reference(dataset_ref, props, ctx) + if table: + identity = f"{schema}.{table}" if schema else table + return identity if _is_physical(identity) else None + path = resolve_dataset_path(props, definitions) + return path if (path and _is_physical(path)) else None diff --git a/src/flowx/parser/lineage.py b/src/flowx/parser/lineage.py new file mode 100644 index 0000000..ace9492 --- /dev/null +++ b/src/flowx/parser/lineage.py @@ -0,0 +1,274 @@ +"""Deterministic lineage extraction from parsed ADF definitions. + +Builds two graphs with no LLM involvement: + * control lineage -- ``ExecutePipeline`` caller -> callee call edges; + * data lineage -- dataset producer -> consumer edges joined on resolved physical identity. +""" + +from __future__ import annotations + +import re +from collections.abc import Iterator +from dataclasses import dataclass +from typing import Any, Literal + +from flowx.models.adf_ast import ( + AdfActivity, + AdfDatasetReference, + AdfDefinitions, + ControlEdge, + DataEdge, + Lineage, +) +from flowx.parser.dataset_resolvers import resolve_dataset_identity + +# Recognized ADF runtime references inside a path expression. Each is a value only +# knowable at runtime; for a *structural* signature we collapse them all to one slot +# token so that, e.g., a writer's ``pipeline().parameters.entityID`` and a reader's +# ``item().entityID`` (the same value passed down a ForEach) produce the same shape. +_PARAM_REF_RE = re.compile( + r"pipeline\(\)\.parameters\.\w+" + r"|item\(\)(?:\.\w+)*" + r"|variables\('[^']*'\)" + r"|dataset\(\)\.\w+" + r"|activity\('[^']*'\)\.[\w.]+" +) +_QUOTED_LITERAL_RE = re.compile(r"'([^']*)'") + + +def _walk_activities(activities: list[AdfActivity]) -> Iterator[AdfActivity]: + """Yield every activity, descending into container children (ForEach/If/Until).""" + for activity in activities: + yield activity + for child in (activity.if_true_activities, activity.if_false_activities, activity.activities): + if child: + yield from _walk_activities(child) + + +def read_execute_pipeline_ref(activity: AdfActivity) -> tuple[str, bool]: + """Return ``(callee_reference_name, wait_on_completion)`` for an ExecutePipeline. + + Reads the same ADF fields the convert-time translator reads, so parser and + translator agree on the raw values. ``callee_reference_name`` is the raw + string (empty when absent); expression resolution is the caller's concern. + """ + props = activity.type_properties or {} + ref = props.get("pipeline", {}) + if isinstance(ref, dict): + name = ref.get("referenceName", "") or "" + else: + name = str(ref) + wait = bool(props.get("waitOnCompletion", True)) + return name, wait + + +def _build_control_edges(definitions: AdfDefinitions) -> list[ControlEdge]: + edges: list[ControlEdge] = [] + for pipeline in definitions.pipelines: + for activity in _walk_activities(pipeline.activities): + if activity.type != "ExecutePipeline": + continue + callee_raw, wait = read_execute_pipeline_ref(activity) + if not callee_raw: + continue + resolved = definitions.get_pipeline(callee_raw) + callee_name = resolved.name if resolved is not None else callee_raw + edges.append( + ControlEdge( + caller_pipeline=pipeline.name, + callee_pipeline=callee_name, + activity_name=activity.name, + wait_on_completion=wait, + ) + ) + return edges + + +@dataclass(slots=True, kw_only=True) +class _DatasetEndpoint: + pipeline: str + activity: str + dataset_name: str + identity: str | None + path_signature: str | None + + +def _normalize_path_expression(expr: Any) -> str | None: + """Reduce a (possibly parameterized) ADF path expression to a structural signature. + + Keeps the literal path segments and collapses every runtime reference + (``pipeline().parameters.X``, ``item().X``, ``variables(...)``, ``dataset().X``, + ``activity(...).output...``) to a single ``

`` slot. The result captures the + path *shape* (literal skeleton + slot count) without guessing the runtime value. + Returns ``None`` when there is no literal segment to anchor on (a signature of + only slots is too weak to be a meaningful join key -- never guess). + + Known limitation: literal fragments are concatenated without positional + information, so two expressions that use the *same* literal segments in a + *different* order around their slots (e.g. ``@concat('/data/',p.x,'/rpt/')`` + vs ``@concat(p.y,'/data/','/rpt/')``) collapse to the same signature. This is + rare in practice and the expression tier is deliberately the lower-confidence + match (see ``_build_data_edges``); the agentic enrichment pass weighs it. + """ + if isinstance(expr, dict): + expr = expr.get("value", "") + if not isinstance(expr, str) or not expr.strip(): + return None + text = expr.strip() + if "@" not in text: + # A bare literal value (no ADF expression) — the whole string is the literal + # path/filename, with no runtime slots. + literal = re.sub(r"/+", "/", text).strip("/") + return f"{literal}|slots=0" if literal else None + marked = _PARAM_REF_RE.sub("

", text) + literal = re.sub(r"/+", "/", "".join(_QUOTED_LITERAL_RE.findall(marked))).strip("/") + if not literal: + return None + return f"{literal}|slots={marked.count('

')}" + + +def _path_signature(parameters: dict[str, Any] | None) -> str | None: + """Structural signature of a dataset reference's parameterized folderPath/fileName. + + Requires a resolvable **folderPath** literal anchor. The file name alone is too + weak a discriminator: many unrelated activities write ``.csv``/``.json`` files to + opaque parameterized folders, so a signature built only from a file extension + (``FP[None]/FN[.csv|slots=1]``) would join them all — re-creating the very + explosion the identity-only join was introduced to avoid. Anchoring on the literal + folder segment keeps the match specific to a real, named location. + + ``None`` when the folder path has no literal segment to anchor on. + """ + if not parameters: + return None + folder_sig = _normalize_path_expression(parameters.get("folderPath")) + if folder_sig is None: + return None + file_sig = _normalize_path_expression(parameters.get("fileName")) + return f"FP[{folder_sig}]/FN[{file_sig}]" + + +def _typeprops_dataset_ref(candidate: object) -> AdfDatasetReference | None: + """Build an AdfDatasetReference from a typeProperties source/sink/dataset slot. + + Carries the reference's ``parameters`` (Lookup/Delete/GetMetadata put the + dataset call-site params here) so the path signature can be computed. + """ + if isinstance(candidate, dict): + name = candidate.get("referenceName") + if isinstance(name, str) and name: + params = candidate.get("parameters") + return AdfDatasetReference( + reference_name=name, + parameters=params if isinstance(params, dict) else None, + ) + return None + + +def _activity_dataset_refs(activity: AdfActivity, *, produced: bool) -> Iterator[AdfDatasetReference]: + """Yield dataset references an activity writes (produced) or reads (not produced). + + A dataset named in both an activity-level slot (``inputs``/``outputs``) and its + ``typeProperties`` (``source``/``sink``/``dataset``) is yielded once, so a single + activity does not create duplicate identical edges for the same dataset. + """ + props = activity.type_properties or {} + candidates: list[AdfDatasetReference] = [] + if produced: + candidates.extend(activity.outputs or []) + sink_ref = _typeprops_dataset_ref(props.get("sink")) + if sink_ref is not None: + candidates.append(sink_ref) + else: + candidates.extend(activity.inputs or []) + for key in ("source", "dataset"): + read_ref = _typeprops_dataset_ref(props.get(key)) + if read_ref is not None: + candidates.append(read_ref) + + seen: set[str] = set() + for ref in candidates: + if ref.reference_name in seen: + continue + seen.add(ref.reference_name) + yield ref + + +def _build_data_edges(definitions: AdfDefinitions) -> list[DataEdge]: + # TODO: the producer x consumer join below is O(producers x consumers). Fine for + # today's factories; if one ever has thousands of same-signature endpoints, bucket + # producers/consumers by (identity or path_signature) and join within buckets. + producers: list[_DatasetEndpoint] = [] + consumers: list[_DatasetEndpoint] = [] + for pipeline in definitions.pipelines: + for activity in _walk_activities(pipeline.activities): + for produced, bucket in ((True, producers), (False, consumers)): + for ref in _activity_dataset_refs(activity, produced=produced): + bucket.append( + _DatasetEndpoint( + pipeline=pipeline.name, + activity=activity.name, + dataset_name=ref.reference_name, + identity=resolve_dataset_identity(ref, definitions), + path_signature=_path_signature(ref.parameters), + ) + ) + + # Two deterministic join tiers, in confidence order. We deliberately do NOT fall back + # to the ADF dataset NAME: a single parameterized dataset is commonly reused across many + # activities that each point it at a DIFFERENT physical file, so a name join manufactures + # false hand-offs. + # + # 1. identity -- both ends resolve to the SAME physical asset (schema.table / path). + # High confidence; a literal, provable hand-off. + # 2. expression -- both ends build the SAME normalized path signature from parameterized + # expressions (same literal path skeleton + slot shape). The runtime + # value is unknown, but the structural match is a real coupling signal + # (e.g. a watermark file written and read back per loop key). Lower + # confidence; anchored on a literal path segment so it does not + # over-match unrelated datasets that merely share a name. + # + # An endpoint that qualifies for identity is matched there and NOT re-matched by + # expression, so each (producer, consumer) pair yields at most one edge. + edges: list[DataEdge] = [] + for prod in producers: + for cons in consumers: + if prod.pipeline == cons.pipeline and prod.activity == cons.activity: + continue + if prod.identity is not None and prod.identity == cons.identity: + edges.append(_data_edge(prod, cons, match_kind="identity", match_key=prod.identity)) + elif ( + prod.identity is None + and cons.identity is None + and prod.path_signature is not None + and prod.path_signature == cons.path_signature + ): + edges.append(_data_edge(prod, cons, match_kind="expression", match_key=prod.path_signature)) + return edges + + +def _data_edge( + prod: _DatasetEndpoint, + cons: _DatasetEndpoint, + *, + match_kind: Literal["identity", "expression"], + match_key: str | None, +) -> DataEdge: + return DataEdge( + dataset_name=prod.dataset_name, + identity=prod.identity, + producer_pipeline=prod.pipeline, + producer_activity=prod.activity, + consumer_pipeline=cons.pipeline, + consumer_activity=cons.activity, + match_kind=match_kind, + match_key=match_key, + ) + + +def build_lineage(definitions: AdfDefinitions) -> Lineage: + """Assemble deterministic control and data lineage graphs.""" + return Lineage( + control_edges=_build_control_edges(definitions), + data_edges=_build_data_edges(definitions), + ) diff --git a/src/flowx/sources/adf/loader.py b/src/flowx/sources/adf/loader.py index af9917a..b14e365 100644 --- a/src/flowx/sources/adf/loader.py +++ b/src/flowx/sources/adf/loader.py @@ -27,8 +27,10 @@ AdfVariable, Inventory, InventoryItem, + Lineage, TranslationStrategy, ) +from flowx.parser.lineage import build_lineage logger = logging.getLogger(__name__) @@ -242,6 +244,7 @@ def build_inventory(definitions: AdfDefinitions) -> Inventory: agentic_count=agentic, unsupported_count=unsupported, pipeline_count=len(definitions.pipelines), + lineage=build_lineage(definitions), ) @@ -771,6 +774,36 @@ def _classify_activities( # --------------------------------------------------------------------------- +def _lineage_to_dict(lineage: "Lineage | None") -> dict[str, list[dict[str, Any]]]: + """Serialise a Lineage to JSON-friendly lists (empty lists, never null).""" + control = lineage.control_edges if lineage else [] + data = lineage.data_edges if lineage else [] + return { + "control_edges": [ + { + "caller_pipeline": e.caller_pipeline, + "callee_pipeline": e.callee_pipeline, + "activity_name": e.activity_name, + "wait_on_completion": e.wait_on_completion, + } + for e in control + ], + "data_edges": [ + { + "dataset_name": e.dataset_name, + "identity": e.identity, + "producer_pipeline": e.producer_pipeline, + "producer_activity": e.producer_activity, + "consumer_pipeline": e.consumer_pipeline, + "consumer_activity": e.consumer_activity, + "match_kind": e.match_kind, + "match_key": e.match_key, + } + for e in data + ], + } + + def _inventory_to_dict(inventory: Inventory, source_dir: str) -> dict[str, Any]: """Serialise an :class:`Inventory` to a JSON-friendly dictionary. @@ -807,6 +840,7 @@ def _inventory_to_dict(inventory: Inventory, source_dir: str) -> dict[str, Any]: "unsupported_count": inventory.unsupported_count, "coverage_pct": coverage_pct, }, + "lineage": _lineage_to_dict(inventory.lineage), } diff --git a/src/flowx/sources/adf/translators/copy.py b/src/flowx/sources/adf/translators/copy.py index fc65bc9..f842f8d 100644 --- a/src/flowx/sources/adf/translators/copy.py +++ b/src/flowx/sources/adf/translators/copy.py @@ -8,10 +8,13 @@ from flowx.models.adf_ast import AdfActivity, AdfDefinitions from flowx.models.ir import Activity, CopyActivity, TranslationContext -from flowx.parser.expression_parser import ( - resolve_expression, - resolve_interpolated_string, - resolve_interpolated_string_for_notebook, +from flowx.parser.dataset_resolvers import ( + dataset_props, + resolve_dataset_linked_service_name, + resolve_dataset_path, + resolve_param_value, + resolve_storage_account, + resolve_table_reference, ) from flowx.sources.adf.query_analysis import analyze_copy_query, dialect_for_source_type @@ -34,11 +37,6 @@ "GoogleCloudStorageLocation": "gs://{bucket}/", } -# Regex to pull AccountName=... from an Azure storage connection string when -# the secret value is plaintext (rare in az exports, but supported). -_ACCOUNT_NAME_RE = re.compile(r"AccountName=([A-Za-z0-9]+)", re.IGNORECASE) -_DATASET_PARAM_RE = re.compile(r"^@dataset\(\)\.([A-Za-z_][A-Za-z0-9_]*)$") - # Database connection-string parsers: pull host/port/database from an ADF linked service's # connectionString. Tolerant of casing/whitespace (ADF accepts Server= and server=). _AZURE_SQL_SERVER_RE = re.compile(r"\bServer=(?:tcp:)?([^,;]+?)(?:,(\d+))?(?:;|$)", re.IGNORECASE) @@ -92,118 +90,12 @@ class SinkPathInfo: uc_volume_path: str -def _dataset_props(dataset_ref: Any, definitions: AdfDefinitions) -> dict[str, Any] | None: - """Return the ``properties`` dict for an input/output dataset reference.""" - dataset = definitions.datasets.get(dataset_ref.reference_name) - if not dataset: - return None - return dict(dataset.properties or {}) - - def _sanitize_volume_name(value: str) -> str: """Sanitises an ADF container name for use as a UC volume name.""" cleaned = re.sub(r"[^A-Za-z0-9_]", "_", value or "default_volume").strip("_") return cleaned or "default_volume" -def _resolve_param_value( - raw: Any, - dataset_params: dict[str, Any], - context: TranslationContext, - *, - for_notebook: bool = False, -) -> str: - """Resolves a single ADF location field to a string.""" - if raw is None: - return "" - if isinstance(raw, dict) and raw.get("type") == "Expression": - raw = raw.get("value", "") - if isinstance(raw, (list, dict)): - return "" - if not isinstance(raw, str): - return str(raw) - text = raw - - match = _DATASET_PARAM_RE.match(text.strip()) - if match: - param_name = match.group(1) - return _resolve_param_value( - dataset_params.get(param_name, ""), dataset_params, context, for_notebook=for_notebook - ) - - if "@{" in text: - if for_notebook: - return resolve_interpolated_string_for_notebook(text, context) - return resolve_interpolated_string(text, context) - - if text.startswith("@"): - result = resolve_expression(text, context) - if result is not None and result.kind in ("literal", "dab_ref"): - return result.value - return text - - return text - - -def _resolve_storage_account(linked_service: Any) -> str | None: - """Tries to pull a storage account name out of a linked service, if present.""" - if linked_service is None: - return None - type_props = linked_service.properties.get("typeProperties") or linked_service.properties - - url = type_props.get("url") or "" - if isinstance(url, str) and url: - host = url.replace("https://", "").split("/", 1)[0] - host_no_port = host.split(":", 1)[0] - if "." in host_no_port: - return host_no_port.split(".", 1)[0] - - sas_uri = type_props.get("sasUri") or "" - if isinstance(sas_uri, str) and sas_uri: - host = sas_uri.split("?", 1)[0].replace("https://", "").split("/", 1)[0] - if "." in host: - return host.split(".", 1)[0] - - # Plaintext connection string (rare in az exports — usually masked). - conn_string = type_props.get("connectionString") - if isinstance(conn_string, str): - match = _ACCOUNT_NAME_RE.search(conn_string) - if match: - return match.group(1) - if isinstance(conn_string, dict): - value = conn_string.get("value", "") - match = _ACCOUNT_NAME_RE.search(value) - if match: - return match.group(1) - - # AWS — bucket name lives on the dataset, account is implicit. - # Nothing useful to return at the linked-service level for S3/GCS. - return None - - -def _resolve_dataset_path(dataset_props: dict[str, Any], definitions: AdfDefinitions) -> str | None: - """Resolves a dataset's storage path using its location + linked service.""" - type_props = dataset_props.get("typeProperties") or dataset_props - location = type_props.get("location") or {} - - file_system = location.get("fileSystem") or location.get("container") or "" - folder_path = location.get("folderPath") or "" - if isinstance(file_system, dict) or isinstance(folder_path, dict): - return None # parameterised; caller handles via _resolve_path_info - - linked_service_ref = dataset_props.get("linkedServiceName") or {} - if isinstance(linked_service_ref, dict): - linked_service_name = linked_service_ref.get("referenceName", "") - else: - linked_service_name = str(linked_service_ref) - linked_service = definitions.linked_services.get(linked_service_name) if linked_service_name else None - account = _resolve_storage_account(linked_service) - if not account: - return None - - return f"abfss://{file_system}@{account}.dfs.core.windows.net/{folder_path}".rstrip("/") - - def _resolve_path_info( dataset_ref: Any, dataset_props: dict[str, Any], @@ -227,13 +119,13 @@ def _resolve_path_info( # Container name goes in the volume URL (no expressions allowed); other path components flow into # the notebook write as f-string fragments so date/time expressions evaluate at runtime. - container = _resolve_param_value( + container = resolve_param_value( location.get("container") or location.get("fileSystem") or location.get("bucketName"), effective, context, ) - folder = _resolve_param_value(location.get("folderPath"), effective, context, for_notebook=True).strip("/") - filename = _resolve_param_value(location.get("fileName"), effective, context, for_notebook=True).strip("/") + folder = resolve_param_value(location.get("folderPath"), effective, context, for_notebook=True).strip("/") + filename = resolve_param_value(location.get("fileName"), effective, context, for_notebook=True).strip("/") if not container: return None @@ -244,7 +136,7 @@ def _resolve_path_info( else: linked_service_name = str(linked_service_ref) linked_service = definitions.linked_services.get(linked_service_name) if linked_service_name else None - storage_account = _resolve_storage_account(linked_service) if linked_service else None + storage_account = resolve_storage_account(linked_service) if linked_service else None if location_type in ("AmazonS3Location", "GoogleCloudStorageLocation"): external_url = _LOCATION_URL_TEMPLATE[location_type].format(bucket=container) @@ -297,122 +189,6 @@ def _extract_source_query_text(source_properties: dict[str, Any]) -> str | None: return None -def _effective_dataset_params(dataset_ref: Any, dataset_props: dict[str, Any]) -> dict[str, Any]: - """Returns the effective parameter map for a dataset reference. - - Args: - dataset_ref: Activity-side dataset reference (carries parameter - overrides supplied at the call site). - dataset_props: Full properties dict of the referenced dataset. - - Returns: - Mapping of parameter name to resolved value: dataset declared - defaults first, then activity-side overrides win. - """ - declared = dataset_props.get("parameters") or {} - effective: dict[str, Any] = {} - for name, spec in declared.items(): - if isinstance(spec, dict) and "defaultValue" in spec: - effective[name] = spec["defaultValue"] - if dataset_ref is not None and getattr(dataset_ref, "parameters", None): - effective.update(dict(dataset_ref.parameters)) - return effective - - -def _resolve_table_reference( - dataset_ref: Any, - dataset_props: dict[str, Any] | None, - context: TranslationContext, -) -> tuple[str | None, str | None]: - """Resolves the schema and table name from a dataset reference. - - Args: - dataset_ref: Activity-side dataset reference. - dataset_props: Full properties dict of the referenced dataset. - context: Translation context for expression resolution. - - Returns: - Tuple of ``(schema, table)`` strings. Either may be ``None`` - when the dataset does not carry that field. ADF parameter - expressions are resolved against the dataset reference's - effective parameter map. Handles both the nested - ``typeProperties`` shape and the ``schemaTypePropertiesSchema`` - flattened form ``az datafactory dataset show`` emits. - """ - if not dataset_props: - return None, None - type_props = dataset_props.get("typeProperties") if isinstance(dataset_props.get("typeProperties"), dict) else None - effective_params = _effective_dataset_params(dataset_ref, dataset_props) - schema_raw = _pick_dataset_field( - type_props, - dataset_props, - ("schema", "database"), - ("schemaTypePropertiesSchema", "database"), - ) - table_raw = _pick_dataset_field( - type_props, - dataset_props, - ("table", "tableName"), - ("table", "tableName"), - ) - schema = _resolve_param_value(schema_raw, effective_params, context) if schema_raw is not None else None - table = _resolve_param_value(table_raw, effective_params, context) if table_raw is not None else None - return (schema or None), (table or None) - - -def _pick_dataset_field( - type_props: dict[str, Any] | None, - dataset_props: dict[str, Any], - nested_keys: tuple[str, ...], - flat_keys: tuple[str, ...], -) -> Any: - """Returns the first populated dataset field across nested and flat shapes. - - Args: - type_props: ``typeProperties`` dict when present, ``None`` - when the dataset is in the az-flattened shape. - dataset_props: Top-level dataset properties dict. - nested_keys: Keys to try inside ``type_props`` (nested ADF shape). - flat_keys: Keys to try at the top level (az flattened shape). - - Returns: - The first non-empty value found. Empty strings, empty lists, - and ``None`` are skipped so column-schema artifacts like - ``schema: []`` don't shadow the actual database schema stored - under a flattened key. - """ - candidates: list[Any] = [] - if type_props is not None: - candidates.extend(type_props.get(key) for key in nested_keys) - candidates.extend(dataset_props.get(key) for key in flat_keys) - for value in candidates: - if value is None: - continue - if isinstance(value, (list, dict)) and not value: - continue - if isinstance(value, str) and not value.strip(): - continue - return value - return None - - -def _resolve_dataset_linked_service_name(dataset_props: dict[str, Any] | None) -> str | None: - """Returns the linked service name a dataset references. - - Args: - dataset_props: Full properties dict of the referenced dataset. - - Returns: - Linked service name string, or ``None`` when not present. - """ - if not dataset_props: - return None - raw = dataset_props.get("linkedServiceName") or {} - if isinstance(raw, dict): - return raw.get("referenceName") or None - return str(raw) or None - - def _resolve_database_connection( linked_service_name: str, definitions: AdfDefinitions, @@ -518,10 +294,10 @@ def _resolve_source_path(activity: AdfActivity, definitions: AdfDefinitions) -> """Resolves the full storage path from the activity's input dataset.""" if not activity.inputs: return None - props = _dataset_props(activity.inputs[0], definitions) + props = dataset_props(activity.inputs[0], definitions) if not props: return None - return _resolve_dataset_path(props, definitions) + return resolve_dataset_path(props, definitions) def translate( @@ -553,9 +329,9 @@ def translate( if activity.inputs: source_dataset_ref = activity.inputs[0] - source_dataset_props = _dataset_props(source_dataset_ref, definitions) - source_schema, source_table = _resolve_table_reference(source_dataset_ref, source_dataset_props, context) - source_ls_name = _resolve_dataset_linked_service_name(source_dataset_props) + source_dataset_props = dataset_props(source_dataset_ref, definitions) + source_schema, source_table = resolve_table_reference(source_dataset_ref, source_dataset_props, context) + source_ls_name = resolve_dataset_linked_service_name(source_dataset_props) if source_schema: source_properties["source_schema"] = source_schema if source_table: @@ -611,7 +387,7 @@ def translate( sink_table_name: str | None = None if activity.outputs: sink_dataset_ref = activity.outputs[0] - sink_dataset_props = _dataset_props(sink_dataset_ref, definitions) + sink_dataset_props = dataset_props(sink_dataset_ref, definitions) if sink_dataset_props: sink_dataset_type = sink_dataset_props.get("type") sink_format = _DATASET_TYPE_TO_SPARK_FORMAT.get(sink_dataset_type or "") @@ -633,10 +409,10 @@ def translate( else: # Non-file sinks: fall back to the simpler resolver (returns # an abfss:// path or None for tables). - sink_resolved_path = _resolve_dataset_path(sink_dataset_props, definitions) + sink_resolved_path = resolve_dataset_path(sink_dataset_props, definitions) - sink_schema, sink_table_name = _resolve_table_reference(sink_dataset_ref, sink_dataset_props, context) - sink_ls_name = _resolve_dataset_linked_service_name(sink_dataset_props) + sink_schema, sink_table_name = resolve_table_reference(sink_dataset_ref, sink_dataset_props, context) + sink_ls_name = resolve_dataset_linked_service_name(sink_dataset_props) if sink_schema: sink_properties["schema"] = sink_schema if sink_ls_name: diff --git a/src/flowx/sources/adf/translators/execute_pipeline.py b/src/flowx/sources/adf/translators/execute_pipeline.py index dfe169d..82f456a 100644 --- a/src/flowx/sources/adf/translators/execute_pipeline.py +++ b/src/flowx/sources/adf/translators/execute_pipeline.py @@ -7,6 +7,7 @@ from flowx.models.adf_ast import AdfActivity, AdfDefinitions from flowx.models.ir import Activity, ExecutePipelineActivity, TranslationContext from flowx.parser.expression_parser import resolve_expression +from flowx.parser.lineage import read_execute_pipeline_ref from flowx.sources.adf.translators.resolve import resolve_field @@ -30,11 +31,8 @@ def translate( type_properties = activity.type_properties or {} pipeline_ref = type_properties.get("pipeline", {}) - pipeline_name = ( - resolve_field(pipeline_ref.get("referenceName", ""), context) - if isinstance(pipeline_ref, dict) - else str(pipeline_ref) - ) + raw_reference_name, wait_on_completion = read_execute_pipeline_ref(activity) + pipeline_name = resolve_field(raw_reference_name, context) if isinstance(pipeline_ref, dict) else raw_reference_name raw_parameters = type_properties.get("parameters") or {} parameters: dict[str, str] = {} @@ -44,8 +42,6 @@ def translate( if resolved_value is not None: parameters[name] = resolved_value - wait_on_completion = type_properties.get("waitOnCompletion", True) - if approximations: # Stamp the approximations onto the activity so the bundler can # surface them in SETUP.md. diff --git a/tests/unit/test_adf_loader.py b/tests/unit/test_adf_loader.py index 32e8821..488b6a4 100644 --- a/tests/unit/test_adf_loader.py +++ b/tests/unit/test_adf_loader.py @@ -463,3 +463,81 @@ def test_idempotent_on_empty_dir(self, tmp_path): """Clearing a directory with no orchestra artifacts is a no-op (no error).""" clear_stale_outputs(tmp_path) assert list(tmp_path.iterdir()) == [] + + +# --------------------------------------------------------------------------- +# AdfDefinitions.get_pipeline +# --------------------------------------------------------------------------- + + +class TestGetPipeline: + def test_get_pipeline_exact_and_case_insensitive(self, adf_definitions): + first = adf_definitions.pipelines[0] + assert adf_definitions.get_pipeline(first.name) is first + assert adf_definitions.get_pipeline(first.name.upper()) is first + assert adf_definitions.get_pipeline("no_such_pipeline") is None + assert adf_definitions.get_pipeline(None) is None + + +# --------------------------------------------------------------------------- +# Lineage models +# --------------------------------------------------------------------------- + + +class TestLineageModels: + def test_lineage_defaults_to_empty_lists(self): + from flowx.models.adf_ast import Lineage + + lin = Lineage() + assert lin.control_edges == [] + assert lin.data_edges == [] + + def test_control_and_data_edges_construct(self): + from flowx.models.adf_ast import ControlEdge, DataEdge + + ce = ControlEdge(caller_pipeline="P", callee_pipeline="C", activity_name="Run C") + assert ce.wait_on_completion is True + de = DataEdge( + dataset_name="ds", + identity="s.t", + producer_pipeline="P", + producer_activity="w", + consumer_pipeline="Q", + consumer_activity="r", + ) + assert de.identity == "s.t" + + +# --------------------------------------------------------------------------- +# Inventory serialization (lineage) +# --------------------------------------------------------------------------- + + +class TestInventorySerialization: + def test_inventory_dict_has_lineage(self, adf_definitions): + from flowx.parser.adf_loader import _inventory_to_dict, build_inventory + + inv = build_inventory(adf_definitions) + d = _inventory_to_dict(inv, source_dir="/tmp/src") + assert "lineage" in d + assert isinstance(d["lineage"]["control_edges"], list) + assert isinstance(d["lineage"]["data_edges"], list) + + def test_lineage_edges_serialize_expected_keys(self, adf_definitions): + from flowx.parser.adf_loader import _inventory_to_dict, build_inventory + + inv = build_inventory(adf_definitions) + d = _inventory_to_dict(inv, source_dir="/tmp/src") + control = {(e["caller_pipeline"], e["callee_pipeline"]) for e in d["lineage"]["control_edges"]} + assert ("pipeline_execute_pipeline_nested", "pipeline_copy_sql_to_delta") in control + for e in d["lineage"]["control_edges"]: + assert set(e) == {"caller_pipeline", "callee_pipeline", "activity_name", "wait_on_completion"} + + def test_empty_inventory_emits_empty_lists_not_null(self): + from flowx.models.adf_ast import AdfDefinitions + from flowx.parser.adf_loader import _inventory_to_dict, build_inventory + + defs = AdfDefinitions(pipelines=[], datasets={}, linked_services={}, triggers=[]) + inv = build_inventory(defs) + d = _inventory_to_dict(inv, source_dir="/tmp/src") + assert d["lineage"] == {"control_edges": [], "data_edges": []} diff --git a/tests/unit/test_dataset_resolvers.py b/tests/unit/test_dataset_resolvers.py new file mode 100644 index 0000000..486422b --- /dev/null +++ b/tests/unit/test_dataset_resolvers.py @@ -0,0 +1,74 @@ +"""Unit tests for the shared dataset-identity resolvers.""" + +from __future__ import annotations + +from flowx.models.adf_ast import ( + AdfDataset, + AdfDatasetReference, + AdfDefinitions, +) +from flowx.parser.dataset_resolvers import resolve_dataset_identity + + +def _defs_with_table_dataset(name: str, table: str) -> AdfDefinitions: + ds = AdfDataset( + name=name, + type="AzureDatabricksDeltaLakeDataset", + properties={ + "type": "AzureDatabricksDeltaLakeDataset", + "linkedServiceName": {"referenceName": "ls_x", "type": "LinkedServiceReference"}, + "typeProperties": {"table": table}, + }, + ) + return AdfDefinitions(pipelines=[], datasets={name: ds}, linked_services={}, triggers=[]) + + +class TestResolveDatasetIdentity: + def test_table_identity_resolves(self): + defs = _defs_with_table_dataset("ds_orders", "curated.orders") + ref = AdfDatasetReference(reference_name="ds_orders") + assert resolve_dataset_identity(ref, defs) == "curated.orders" + + def test_unknown_dataset_returns_none(self): + defs = AdfDefinitions(pipelines=[], datasets={}, linked_services={}, triggers=[]) + ref = AdfDatasetReference(reference_name="missing") + assert resolve_dataset_identity(ref, defs) is None + + def test_none_ref_returns_none(self): + defs = AdfDefinitions(pipelines=[], datasets={}, linked_services={}, triggers=[]) + assert resolve_dataset_identity(None, defs) is None + + # --- Parameterized-table regression tests --- + + def test_parameterized_table_pipeline_param_returns_none(self): + """ADF expression referencing a pipeline parameter must return None, not a DAB placeholder.""" + defs = _defs_with_table_dataset("ds_param", "@pipeline().parameters.tableName") + ref = AdfDatasetReference(reference_name="ds_param") + assert resolve_dataset_identity(ref, defs) is None + + def test_parameterized_table_interpolated_returns_none(self): + """Interpolated ADF expression (tbl_@{...}) must return None, not a DAB placeholder.""" + defs = _defs_with_table_dataset("ds_interp", "tbl_@{pipeline().parameters.suffix}") + ref = AdfDatasetReference(reference_name="ds_interp") + assert resolve_dataset_identity(ref, defs) is None + + def test_parameterized_table_expression_object_returns_none(self): + """Expression-object typeProperties.table must return None, not a DAB placeholder.""" + ds = AdfDataset( + name="ds_expr", + type="AzureDatabricksDeltaLakeDataset", + properties={ + "type": "AzureDatabricksDeltaLakeDataset", + "linkedServiceName": {"referenceName": "ls_x", "type": "LinkedServiceReference"}, + "typeProperties": {"table": {"type": "Expression", "value": "@pipeline().parameters.t"}}, + }, + ) + defs = AdfDefinitions(pipelines=[], datasets={"ds_expr": ds}, linked_services={}, triggers=[]) + ref = AdfDatasetReference(reference_name="ds_expr") + assert resolve_dataset_identity(ref, defs) is None + + def test_literal_table_still_resolves(self): + """Literal schema.table must continue to return 'curated.orders' (non-regression).""" + defs = _defs_with_table_dataset("ds_orders2", "curated.orders") + ref = AdfDatasetReference(reference_name="ds_orders2") + assert resolve_dataset_identity(ref, defs) == "curated.orders" diff --git a/tests/unit/test_lineage.py b/tests/unit/test_lineage.py new file mode 100644 index 0000000..de93397 --- /dev/null +++ b/tests/unit/test_lineage.py @@ -0,0 +1,513 @@ +"""Unit tests for deterministic lineage extraction (lineage.py).""" + +from __future__ import annotations + +from flowx.models.adf_ast import AdfActivity, AdfDataset, AdfDatasetReference, AdfDefinitions, AdfPipeline +from flowx.parser.adf_loader import load_adf_definitions +from flowx.parser.lineage import build_lineage, read_execute_pipeline_ref + + +def _ep(name: str, callee: str, *, wait: bool = True) -> AdfActivity: + """An ExecutePipeline activity calling `callee`.""" + return AdfActivity( + name=name, + type="ExecutePipeline", + type_properties={ + "pipeline": {"referenceName": callee, "type": "PipelineReference"}, + "waitOnCompletion": wait, + }, + ) + + +def _pipeline(name: str, activities: list[AdfActivity]) -> AdfPipeline: + return AdfPipeline(name=name, activities=activities) + + +def _foreach(name: str, children: list[AdfActivity]) -> AdfActivity: + """A ForEach container wrapping `children` (uses the `.activities` slot the walker recurses).""" + return AdfActivity( + name=name, + type="ForEach", + type_properties={"items": {"value": "@pipeline().parameters.items", "type": "Expression"}}, + activities=children, + ) + + +class TestControlEdges: + def test_flat_execute_pipeline_edges_from_shared_fixture(self, fixtures_dir): + # Reuses the EXISTING shared fixture (not a new one): 3 ExecutePipeline activities. + defs = load_adf_definitions(fixtures_dir) + lineage = build_lineage(defs) + edges = { + (e.caller_pipeline, e.callee_pipeline, e.wait_on_completion) + for e in lineage.control_edges + if e.caller_pipeline == "pipeline_execute_pipeline_nested" + } + assert ("pipeline_execute_pipeline_nested", "pipeline_copy_sql_to_delta", True) in edges + assert ("pipeline_execute_pipeline_nested", "pipeline_notebook_with_params", True) in edges + assert ("pipeline_execute_pipeline_nested", "pipeline_delete_recursive", False) in edges + + def test_parent_fans_out_to_children(self): + # Shape of a real parent-orchestrator migration, generic names. + parent = _pipeline( + "orchestrator", + [ + _ep("Run Child A", "child_a"), + _ep("Run Child B", "child_b", wait=False), + ], + ) + child_a = _pipeline("child_a", []) + child_b = _pipeline("child_b", []) + defs = AdfDefinitions(pipelines=[parent, child_a, child_b], datasets={}, linked_services={}, triggers=[]) + lineage = build_lineage(defs) + edges = {(e.caller_pipeline, e.callee_pipeline, e.wait_on_completion) for e in lineage.control_edges} + assert ("orchestrator", "child_a", True) in edges + assert ("orchestrator", "child_b", False) in edges + + def test_nested_execute_pipeline_is_found(self): + # ExecutePipeline nested inside a ForEach must be caught by the recursion. + parent = _pipeline("orchestrator", [_foreach("For Each Key", [_ep("Run Nested", "child_a")])]) + child_a = _pipeline("child_a", []) + defs = AdfDefinitions(pipelines=[parent, child_a], datasets={}, linked_services={}, triggers=[]) + lineage = build_lineage(defs) + assert any( + e.caller_pipeline == "orchestrator" and e.callee_pipeline == "child_a" for e in lineage.control_edges + ) + + def test_unresolved_callee_is_recorded(self): + parent = _pipeline("caller", [_ep("Run Ghost", "not_exported")]) + defs = AdfDefinitions(pipelines=[parent], datasets={}, linked_services={}, triggers=[]) + lineage = build_lineage(defs) + assert any(e.callee_pipeline == "not_exported" for e in lineage.control_edges) + + +class TestReadExecutePipelineRef: + def test_reads_reference_and_wait(self): + act = _ep("Run", "child", wait=False) + assert read_execute_pipeline_ref(act) == ("child", False) + + def test_defaults_wait_true_and_empty_name(self): + act = AdfActivity(name="Run", type="ExecutePipeline", type_properties={}) + assert read_execute_pipeline_ref(act) == ("", True) + + def test_non_dict_pipeline_ref_is_stringified(self): + # ADF normally exports a dict ref, but a bare string must not crash the reader. + act = AdfActivity(name="Run", type="ExecutePipeline", type_properties={"pipeline": "child"}) + assert read_execute_pipeline_ref(act) == ("child", True) + + +class TestDataEdges: + def _delta_dataset(self, name: str, table: str) -> AdfDataset: + from flowx.models.adf_ast import AdfDataset + + return AdfDataset( + name=name, + type="AzureDatabricksDeltaLakeDataset", + properties={ + "type": "AzureDatabricksDeltaLakeDataset", + "linkedServiceName": {"referenceName": "ls_x", "type": "LinkedServiceReference"}, + "typeProperties": {"table": table}, + }, + ) + + def _copy_writes(self, name: str, out_dataset: str) -> AdfActivity: + return AdfActivity( + name=name, + type="Copy", + type_properties={ + "source": {"type": "DelimitedTextSource"}, + "sink": {"type": "AzureDatabricksDeltaLakeSink"}, + }, + outputs=[AdfDatasetReference(reference_name=out_dataset)], + ) + + def _lookup_reads(self, name: str, in_dataset: str) -> AdfActivity: + return AdfActivity( + name=name, + type="Lookup", + type_properties={"dataset": {"referenceName": in_dataset, "type": "DatasetReference"}}, + ) + + def test_producer_consumer_join_on_identity(self): + # Two DIFFERENTLY-NAMED datasets pointing at the same physical table must join. + producer = _pipeline("writer", [self._copy_writes("Write Orders", "ds_orders_out")]) + consumer = _pipeline("reader", [self._lookup_reads("Read Orders", "ds_orders_in")]) + defs = AdfDefinitions( + pipelines=[producer, consumer], + datasets={ + "ds_orders_out": self._delta_dataset("ds_orders_out", "curated.orders"), + "ds_orders_in": self._delta_dataset("ds_orders_in", "curated.orders"), + }, + linked_services={}, + triggers=[], + ) + lineage = build_lineage(defs) + matches = [e for e in lineage.data_edges if e.producer_pipeline == "writer" and e.consumer_pipeline == "reader"] + assert len(matches) == 1 + edge = matches[0] + assert edge.identity == "curated.orders" + assert edge.producer_activity == "Write Orders" + assert edge.consumer_activity == "Read Orders" + + def test_no_self_edge(self): + # A single activity that both writes and reads the same table must not edge to itself. + both = AdfActivity( + name="Merge", + type="Copy", + type_properties={"source": {"type": "DeltaSource"}, "sink": {"type": "AzureDatabricksDeltaLakeSink"}}, + inputs=[AdfDatasetReference(reference_name="ds_same")], + outputs=[AdfDatasetReference(reference_name="ds_same")], + ) + defs = AdfDefinitions( + pipelines=[_pipeline("p", [both])], + datasets={"ds_same": self._delta_dataset("ds_same", "curated.orders")}, + linked_services={}, + triggers=[], + ) + lineage = build_lineage(defs) + for e in lineage.data_edges: + assert not (e.producer_pipeline == e.consumer_pipeline and e.producer_activity == e.consumer_activity) + + def test_unresolved_identity_emits_no_edge(self): + # A dataset with no resolvable table/path => identity None => NO edge. + # Rationale (validated on a real-world factory): a single parameterized dataset is + # commonly reused by many activities pointing at DIFFERENT physical files, so a + # name-based join would manufacture false hand-offs. Only resolved physical + # identity joins; unresolved coupling is knowable only at runtime ("never guess"). + from flowx.models.adf_ast import AdfDataset + + opaque = AdfDataset(name="ds_opaque", type="Unknown", properties={"type": "Unknown"}) + producer = _pipeline("writer", [self._copy_writes("Write", "ds_opaque")]) + consumer = _pipeline("reader", [self._lookup_reads("Read", "ds_opaque")]) + defs = AdfDefinitions( + pipelines=[producer, consumer], + datasets={"ds_opaque": opaque}, + linked_services={}, + triggers=[], + ) + lineage = build_lineage(defs) + matches = [e for e in lineage.data_edges if e.producer_pipeline == "writer" and e.consumer_pipeline == "reader"] + assert matches == [] + + def test_same_name_different_literal_paths_do_not_join(self): + # Two activities reuse ONE parameterized dataset name but write/read DIFFERENT literal + # files (a.csv vs b.csv). Identity is unresolvable; the path signatures differ on their + # literal segment, so neither the identity nor the expression tier joins them. This is + # the dm_dummyDS-style reuse that a naive name-join would wrongly couple. + from flowx.models.adf_ast import AdfDataset + + param_ds = AdfDataset( + name="ds_param", + type="DelimitedText", + properties={ + "type": "DelimitedText", + "parameters": {"fileName": {"type": "string"}}, + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": {"value": "@dataset().fileName", "type": "Expression"}, + } + }, + }, + ) + writer = AdfActivity( + name="WriteLogA", + type="Copy", + type_properties={"source": {"type": "DelimitedTextSource"}, "sink": {"type": "DelimitedTextSink"}}, + outputs=[AdfDatasetReference(reference_name="ds_param", parameters={"fileName": "a.csv"})], + ) + reader = AdfActivity( + name="ReadLogB", + type="Copy", + type_properties={"source": {"type": "DelimitedTextSource"}, "sink": {"type": "DelimitedTextSink"}}, + inputs=[AdfDatasetReference(reference_name="ds_param", parameters={"fileName": "b.csv"})], + ) + defs = AdfDefinitions( + pipelines=[_pipeline("p1", [writer]), _pipeline("p2", [reader])], + datasets={"ds_param": param_ds}, + linked_services={}, + triggers=[], + ) + lineage = build_lineage(defs) + assert lineage.data_edges == [] + + def _param_ds(self, name: str) -> "AdfDataset": # noqa: F821 + # A dataset whose physical path is fully parameterized => identity unresolvable. + from flowx.models.adf_ast import AdfDataset + + return AdfDataset( + name=name, + type="DelimitedText", + properties={ + "type": "DelimitedText", + "parameters": {"folderPath": {"type": "string"}, "fileName": {"type": "string"}}, + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "folderPath": {"value": "@dataset().folderPath", "type": "Expression"}, + "fileName": {"value": "@dataset().fileName", "type": "Expression"}, + } + }, + }, + ) + + def test_expression_signature_join_recovers_watermark_handoff(self): + # The watermark pattern: a writer builds a path from pipeline().parameters.X and a + # reader (inside a ForEach) builds the SAME literal path from item().X. Identity is + # unresolvable on both, but the normalized path signatures match => one expression edge. + wm_folder = "@concat(pipeline().parameters.root,'/config-params/entity-versions/')" + wm_folder_reader = "@concat(pipeline().parameters.root,'/config-params/entity-versions/')" + writer = AdfActivity( + name="update_last_version", + type="Copy", + type_properties={"source": {"type": "DelimitedTextSource"}, "sink": {"type": "DelimitedTextSink"}}, + outputs=[ + AdfDatasetReference( + reference_name="ds_dummy", + parameters={ + "folderPath": wm_folder, + "fileName": "@concat(pipeline().parameters.entityID,'.csv')", + }, + ) + ], + ) + reader = AdfActivity( + name="get_WM_Version", + type="Lookup", + type_properties={ + "dataset": { + "referenceName": "ds_last_version", + "type": "DatasetReference", + "parameters": {"folderPath": wm_folder_reader, "fileName": "@concat(item().entityID,'.csv')"}, + } + }, + ) + defs = AdfDefinitions( + pipelines=[_pipeline("orchestrator", [writer, reader])], + datasets={"ds_dummy": self._param_ds("ds_dummy"), "ds_last_version": self._param_ds("ds_last_version")}, + linked_services={}, + triggers=[], + ) + edges = build_lineage(defs).data_edges + wm = [ + e for e in edges if e.producer_activity == "update_last_version" and e.consumer_activity == "get_WM_Version" + ] + assert len(wm) == 1 + assert wm[0].match_kind == "expression" + assert wm[0].identity is None + assert wm[0].match_key == "FP[config-params/entity-versions|slots=1]/FN[.csv|slots=1]" + + def test_expression_signature_does_not_over_match_different_paths(self): + # Two parameterized writes/reads with DIFFERENT literal path anchors must NOT join, + # even though both are unresolvable and share the same slot shape. This is why the + # dummy-dataset logging writes do not collide with the watermark reads. + writer = AdfActivity( + name="WriteLog", + type="Copy", + type_properties={"source": {"type": "DelimitedTextSource"}, "sink": {"type": "DelimitedTextSink"}}, + outputs=[ + AdfDatasetReference( + reference_name="ds_dummy", + parameters={ + "folderPath": "@concat(pipeline().parameters.root,'/executions/ExtractionLog/')", + "fileName": "@concat(pipeline().parameters.id,'.csv')", + }, + ) + ], + ) + reader = AdfActivity( + name="ReadWatermark", + type="Lookup", + type_properties={ + "dataset": { + "referenceName": "ds_wm", + "type": "DatasetReference", + "parameters": { + "folderPath": "@concat(pipeline().parameters.root,'/config-params/entity-versions/')", + "fileName": "@concat(item().id,'.csv')", + }, + } + }, + ) + defs = AdfDefinitions( + pipelines=[_pipeline("p", [writer, reader])], + datasets={"ds_dummy": self._param_ds("ds_dummy"), "ds_wm": self._param_ds("ds_wm")}, + linked_services={}, + triggers=[], + ) + assert build_lineage(defs).data_edges == [] + + def test_opaque_folder_same_extension_do_not_join(self): + # Two UNRELATED activities: opaque parameterized folders (no literal anchor) and the + # same .csv extension. Their file names would sign, but with no folder-literal anchor + # the signature is suppressed => no edge. Prevents a .csv/.json extension explosion. + writer = AdfActivity( + name="WriteAudit", + type="Copy", + type_properties={"source": {"type": "DelimitedTextSource"}, "sink": {"type": "DelimitedTextSink"}}, + outputs=[ + AdfDatasetReference( + reference_name="ds_audit", + parameters={ + "folderPath": "@pipeline().parameters.auditFolder", + "fileName": "@concat(pipeline().parameters.runId,'.csv')", + }, + ) + ], + ) + reader = AdfActivity( + name="ReadWatermark", + type="Lookup", + type_properties={ + "dataset": { + "referenceName": "ds_wm", + "type": "DatasetReference", + "parameters": { + "folderPath": "@pipeline().parameters.wmFolder", + "fileName": "@concat(item().key,'.csv')", + }, + } + }, + ) + defs = AdfDefinitions( + pipelines=[_pipeline("p1", [writer]), _pipeline("p2", [reader])], + datasets={"ds_audit": self._param_ds("ds_audit"), "ds_wm": self._param_ds("ds_wm")}, + linked_services={}, + triggers=[], + ) + assert build_lineage(defs).data_edges == [] + + def test_expression_join_matches_across_item_property_chain(self): + # A reader referencing item().properties.name (a multi-level chain) must normalize to + # the same single

slot as a writer's pipeline().parameters.X, so the watermark-style + # match still holds regardless of how deep the runtime reference is. + writer = AdfActivity( + name="Write", + type="Copy", + type_properties={"source": {"type": "DelimitedTextSource"}, "sink": {"type": "DelimitedTextSink"}}, + outputs=[ + AdfDatasetReference( + reference_name="ds_w", + parameters={ + "folderPath": "@concat(pipeline().parameters.root,'/state/versions/')", + "fileName": "@concat(pipeline().parameters.id,'.csv')", + }, + ) + ], + ) + reader = AdfActivity( + name="Read", + type="Lookup", + type_properties={ + "dataset": { + "referenceName": "ds_r", + "type": "DatasetReference", + "parameters": { + "folderPath": "@concat(pipeline().parameters.root,'/state/versions/')", + "fileName": "@concat(item().properties.id,'.csv')", + }, + } + }, + ) + defs = AdfDefinitions( + pipelines=[_pipeline("p", [writer, reader])], + datasets={"ds_w": self._param_ds("ds_w"), "ds_r": self._param_ds("ds_r")}, + linked_services={}, + triggers=[], + ) + edges = [e for e in build_lineage(defs).data_edges if e.producer_activity == "Write"] + assert len(edges) == 1 + assert edges[0].match_kind == "expression" + assert edges[0].match_key == "FP[state/versions|slots=1]/FN[.csv|slots=1]" + + def test_identity_match_takes_precedence_over_expression(self): + # When both ends resolve to a physical identity, the edge is match_kind='identity' + # (the higher-confidence tier), not 'expression'. + producer = _pipeline("writer", [self._copy_writes("Write Orders", "ds_orders_out")]) + consumer = _pipeline("reader", [self._lookup_reads("Read Orders", "ds_orders_in")]) + defs = AdfDefinitions( + pipelines=[producer, consumer], + datasets={ + "ds_orders_out": self._delta_dataset("ds_orders_out", "curated.orders"), + "ds_orders_in": self._delta_dataset("ds_orders_in", "curated.orders"), + }, + linked_services={}, + triggers=[], + ) + matches = [e for e in build_lineage(defs).data_edges if e.producer_pipeline == "writer"] + assert len(matches) == 1 + assert matches[0].match_kind == "identity" + assert matches[0].match_key == "curated.orders" + + def _copy_sink_typeprops(self, name: str, sink_dataset: str) -> AdfActivity: + # Producer whose sink dataset is carried in typeProperties.sink.referenceName + # (not activity.outputs) — exercises _typeprops_dataset_ref's producer path. + return AdfActivity( + name=name, + type="Copy", + type_properties={ + "source": {"type": "DelimitedTextSource"}, + "sink": {"referenceName": sink_dataset, "type": "DatasetReference"}, + }, + ) + + def _copy_source_typeprops(self, name: str, source_dataset: str) -> AdfActivity: + # Consumer whose source dataset is carried in typeProperties.source.referenceName + # (not activity.inputs) — exercises _typeprops_dataset_ref's consumer path. + return AdfActivity( + name=name, + type="Copy", + type_properties={ + "source": {"referenceName": source_dataset, "type": "DatasetReference"}, + "sink": {"type": "AzureDatabricksDeltaLakeSink"}, + }, + ) + + def test_no_duplicate_edge_when_ref_in_both_slots(self): + # A producer naming the same dataset in BOTH activity.outputs AND typeProperties.sink + # must yield a single endpoint, not two — so exactly one edge to the consumer. + producer_act = AdfActivity( + name="Write Both", + type="Copy", + type_properties={ + "source": {"type": "DelimitedTextSource"}, + "sink": {"referenceName": "ds_out", "type": "DatasetReference"}, + }, + outputs=[AdfDatasetReference(reference_name="ds_out")], + ) + producer = _pipeline("writer", [producer_act]) + consumer = _pipeline("reader", [self._lookup_reads("Read", "ds_in")]) + defs = AdfDefinitions( + pipelines=[producer, consumer], + datasets={ + "ds_out": self._delta_dataset("ds_out", "curated.orders"), + "ds_in": self._delta_dataset("ds_in", "curated.orders"), + }, + linked_services={}, + triggers=[], + ) + lineage = build_lineage(defs) + matches = [e for e in lineage.data_edges if e.producer_pipeline == "writer" and e.consumer_pipeline == "reader"] + assert len(matches) == 1 + + def test_join_via_copy_sink_and_source_typeprops(self): + # Producer writes via typeProperties.sink; consumer reads via typeProperties.source. + # Two differently-named datasets resolving to the same table must still join. + producer = _pipeline("writer", [self._copy_sink_typeprops("Write Sink", "ds_sink_out")]) + consumer = _pipeline("reader", [self._copy_source_typeprops("Read Source", "ds_source_in")]) + defs = AdfDefinitions( + pipelines=[producer, consumer], + datasets={ + "ds_sink_out": self._delta_dataset("ds_sink_out", "curated.orders"), + "ds_source_in": self._delta_dataset("ds_source_in", "curated.orders"), + }, + linked_services={}, + triggers=[], + ) + lineage = build_lineage(defs) + matches = [e for e in lineage.data_edges if e.producer_pipeline == "writer" and e.consumer_pipeline == "reader"] + assert len(matches) == 1 + edge = matches[0] + assert edge.identity == "curated.orders" + assert edge.producer_activity == "Write Sink" + assert edge.consumer_activity == "Read Source" From 0ae4a2c7aaaf9dbd07d93cc663b330d151b5c145 Mon Sep 17 00:00:00 2001 From: Matthew Moorcroft Date: Tue, 1 Sep 2026 11:22:28 +0100 Subject: [PATCH 2/7] Agentic insights: intent + cross-pipeline relationships in the discover inventory --- .../plans/2026-07-24-discover-insights.md | 1345 +++++++++++++++++ .../2026-07-23-discover-insights-design.md | 449 ++++++ skills/flowx-discover/SKILL.md | 473 +++++- src/flowx/adapter/__main__.py | 70 + src/flowx/mcp/runner.py | 40 +- src/flowx/mcp/server.py | 48 +- src/flowx/models/adf_ast.py | 162 +- src/flowx/parser/lineage.py | 3 - src/flowx/parser/pipeline_insights.py | 371 +++++ src/flowx/reporting/coverage.py | 3 + src/flowx/reporting/results.py | 19 +- src/flowx/sources/adf/loader.py | 4 +- .../adf/translators/execute_pipeline.py | 2 +- tests/unit/test_lineage.py | 5 - tests/unit/test_pipeline_insights.py | 843 +++++++++++ tests/unit/test_reporting_coverage.py | 104 +- tests/unit/test_reporting_results.py | 95 +- uv.lock | 64 +- 18 files changed, 3883 insertions(+), 217 deletions(-) create mode 100644 docs/superpowers/plans/2026-07-24-discover-insights.md create mode 100644 docs/superpowers/specs/2026-07-23-discover-insights-design.md create mode 100644 src/flowx/parser/pipeline_insights.py create mode 100644 tests/unit/test_pipeline_insights.py diff --git a/docs/superpowers/plans/2026-07-24-discover-insights.md b/docs/superpowers/plans/2026-07-24-discover-insights.md new file mode 100644 index 0000000..5970678 --- /dev/null +++ b/docs/superpowers/plans/2026-07-24-discover-insights.md @@ -0,0 +1,1345 @@ +# Agentic Insights in inventory.json Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add an agent-authored `insights` key to `metadata/inventory.json` during the discover phase, validated and merged by a pure tool, so the later convert phase can consume pipeline intent, Databricks patterns, and cross-pipeline relationships. + +**Architecture:** The agent *authors* an `insights` JSON object (judgment: intent, patterns, relationships that annotate #9's deterministic lineage edges); a new pure tool path *enriches* the inventory — it validates the authored JSON against the inventory (foreign keys to pipeline names and lineage edges), and on success appends exactly one `insights` key while re-serializing the rest byte-identically. There is **no LLM inside the tool**. The feature is surfaced through a new `enrich` adapter subcommand and an `enrich` MCP command, and driven by a new Step 5 in the discover skill. + +**Tech Stack:** Python 3.12, `@dataclass(slots=True, kw_only=True)` models, argparse CLI subcommands, FastMCP dispatcher tool, pytest unit tests, ruff + mypy via `make fmt`. + +## Global Constraints + +- **Python version:** 3.12+ (matches repo floor). +- **Dataclasses:** every model uses `@dataclass(slots=True, kw_only=True)` (AGENTS.md Code Style Rules). +- **Byte-identical write:** the enrich write-back MUST use `json.dumps(obj, indent=2)` with **no** `sort_keys`, **no** `default=str`, and **no** trailing newline — exactly matching discover's write at `src/flowx/parser/adf_loader.py:1021` (`inventory_path.write_text(json.dumps(inventory_dict, indent=2), encoding="utf-8")`). Any deviation breaks the "deterministic keys byte-identical" invariant. +- **Validation before I/O:** `enrich_inventory` MUST return the failure result before writing anything when violations exist. On `ok:false` the inventory file is left untouched on disk. +- **No new dependencies:** validator is hand-rolled; use only stdlib (`json`, `pathlib`, `tempfile`) and existing flowx modules. +- **Tests assert structure/schema, never prose.** No live LLM in any test — all insights fixtures are stubbed JSON literals. Unit tests live in `tests/unit/`, fixtures in `tests/resources/json/`. +- **Customer confidentiality:** no real customer names or customer-derived vocabulary in code, tests, fixtures, comments, or commit messages. Use generic placeholders ("Factory A", `entityID`, "dummy dataset"). The forbidden denylist ("a customer factory", "a large factory", `engagementID`, `engagementDBVersions`, `etl-parameters`) lives ONLY in the design doc as a grep reference — never introduce those terms. +- **Test command:** `PYTHONPATH=src uv run pytest tests/unit -v` (or a single node id with `::`). Format/lint: `make fmt` (runs `ruff format`, `ruff check --fix`, `mypy src/flowx/`). +- **`edge_identity` grammar:** for `edge_type="control"` it is the `ControlEdge.activity_name`; for `edge_type="data"` it is the `DataEdge.match_key`. Validation resolves against exactly these keys. +- **Enriched marker:** presence of the top-level `insights` key IS the enriched marker. Do NOT add any `schema_version` field. + +--- + +## File Structure + +**Created:** +- `src/flowx/parser/pipeline_insights.py` — `load_insights`, `validate_insights`, `merge_into_inventory`, `enrich_inventory`. The full validate-then-merge core. +- `tests/unit/test_pipeline_insights.py` — all unit tests for the models + parser module. + +**Modified:** +- `src/flowx/models/adf_ast.py` — add 4 dataclasses (`LineageEdgeRef`, `PipelineInsight`, `PipelineRelationship`, `Insights`) after `Lineage` (currently ends at line 403). +- `src/flowx/adapter/__main__.py` — add the `enrich` subparser (modeled on `record-results`), an `_run_enrich` handler, and dispatch in `main()`. +- `src/flowx/mcp/runner.py` — add `materialize_json(obj)` helper (parallels `materialize_adf_definitions`). +- `src/flowx/mcp/server.py` — add `_cmd_enrich`, register `"enrich"` in `_COMMANDS`, extend the `flowx` tool docstring. +- `src/flowx/reporting/coverage.py` — add a `has_insights` column (optional, gated on the `insights` key existing). +- `tests/unit/test_reporting_coverage.py` — cover the new column (only if Task 7 is done). +- `skills/flowx-discover/SKILL.md` — insert the new Step 5 (author → enrich); renumber existing Steps 5–8; reword the summary step. + +--- + +## Task 1: Insights data models + +**Files:** +- Modify: `src/flowx/models/adf_ast.py` (append after line 403, the end of `class Lineage`) +- Test: `tests/unit/test_pipeline_insights.py` + +**Interfaces:** +- Consumes: nothing (leaf dataclasses). `Literal` is already imported at `adf_ast.py:7`; `dataclass`/`field` at line 5. +- Produces: `LineageEdgeRef(edge_type: Literal["control","data"], edge_identity: str)`; `PipelineInsight(pipeline: str, pattern_name: str|None=None, intent: str|None=None, databricks_pattern: str|None=None, recommended_databricks_features: list[str]=[], conversion_notes: list[str]=[])`; `PipelineRelationship(from_pipeline: str, to_pipeline: str, lineage_edge: LineageEdgeRef, relationship_summary: str|None=None, databricks_pattern: str|None=None, risk_if_ignored: str|None=None)`; `Insights(overview: str|None=None, pipeline_insights: list[PipelineInsight]=[], pipeline_relationships: list[PipelineRelationship]=[])`. + +- [ ] **Step 1: Write the failing test** + +Create `tests/unit/test_pipeline_insights.py` with this first test (imports at top of file): + +```python +"""Tests for agentic insights models, validation, and enrichment (discover phase).""" + +from __future__ import annotations + +import json +from pathlib import Path + +from flowx.models.adf_ast import ( + Insights, + LineageEdgeRef, + PipelineInsight, + PipelineRelationship, +) + + +def test_insights_dataclasses_construct_with_defaults(): + edge = LineageEdgeRef(edge_type="control", edge_identity="Run Ingestion Pipeline") + rel = PipelineRelationship( + from_pipeline="factory_a", to_pipeline="factory_b", lineage_edge=edge + ) + insight = PipelineInsight(pipeline="factory_a") + doc = Insights( + overview="whole factory", + pipeline_insights=[insight], + pipeline_relationships=[rel], + ) + assert doc.pipeline_insights[0].pipeline == "factory_a" + assert doc.pipeline_relationships[0].lineage_edge.edge_type == "control" + assert doc.pipeline_relationships[0].lineage_edge.edge_identity == "Run Ingestion Pipeline" + # optional fields default cleanly + assert insight.recommended_databricks_features == [] + assert insight.conversion_notes == [] + assert rel.relationship_summary is None +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py::test_insights_dataclasses_construct_with_defaults -v` +Expected: FAIL with `ImportError: cannot import name 'Insights' from 'flowx.models.adf_ast'` + +- [ ] **Step 3: Add the dataclasses** + +Append to `src/flowx/models/adf_ast.py` (after line 403, following the existing section-comment style): + +```python +# --------------------------------------------------------------------------- +# Agentic insights (discover phase) -- agent-authored judgment merged into +# inventory.json. References pipelines by name and annotates deterministic +# Lineage edges; carries no facts of its own. +# --------------------------------------------------------------------------- + + +@dataclass(slots=True, kw_only=True) +class LineageEdgeRef: + """A typed reference from a PipelineRelationship to one deterministic edge. + + Attributes: + edge_type: Which lineage graph the edge lives in. + edge_identity: For ``"control"`` -- the ``ControlEdge.activity_name``; + for ``"data"`` -- the ``DataEdge.match_key``. Echoed verbatim from a + real edge so enrichment can resolve it. + """ + + edge_type: Literal["control", "data"] + edge_identity: str + + +@dataclass(slots=True, kw_only=True) +class PipelineInsight: + """Per-pipeline judgment; references a pipeline by name (foreign key).""" + + pipeline: str + pattern_name: str | None = None + intent: str | None = None + databricks_pattern: str | None = None + recommended_databricks_features: list[str] = field(default_factory=list) + conversion_notes: list[str] = field(default_factory=list) + + +@dataclass(slots=True, kw_only=True) +class PipelineRelationship: + """Cross-pipeline judgment; annotates one deterministic lineage edge.""" + + from_pipeline: str + to_pipeline: str + lineage_edge: LineageEdgeRef + relationship_summary: str | None = None + databricks_pattern: str | None = None + risk_if_ignored: str | None = None + + +@dataclass(slots=True, kw_only=True) +class Insights: + """Agent-authored insights merged into inventory.json under the ``insights`` key.""" + + overview: str | None = None + pipeline_insights: list[PipelineInsight] = field(default_factory=list) + pipeline_relationships: list[PipelineRelationship] = field(default_factory=list) +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py::test_insights_dataclasses_construct_with_defaults -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add src/flowx/models/adf_ast.py tests/unit/test_pipeline_insights.py +git commit -m "$(cat <<'EOF' +Add agentic insights dataclasses to adf_ast + +LineageEdgeRef, PipelineInsight, PipelineRelationship, Insights -- the +typed round-trip side of the discover-phase insights block. + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Task 2: The `validate_insights` pure validator + +**Files:** +- Create: `src/flowx/parser/pipeline_insights.py` +- Test: `tests/unit/test_pipeline_insights.py` + +**Interfaces:** +- Consumes: an `inventory` dict shaped like discover's `_inventory_to_dict` output — `inventory["pipelines"]` is a list of `{"name": str, "activities": [...]}`; `inventory["lineage"]["control_edges"]` is a list of `{"caller_pipeline","callee_pipeline","activity_name","wait_on_completion"}`; `inventory["lineage"]["data_edges"]` is a list of `{"dataset_name","identity","producer_pipeline","producer_activity","consumer_pipeline","consumer_activity","match_kind","match_key"}`. +- Produces: `validate_insights(raw: dict, inventory: dict) -> list[str]` — returns a list of human-readable violation strings; empty list means valid. Collects ALL violations (never fail-fast). + +- [ ] **Step 1: Write the failing tests** + +Add to `tests/unit/test_pipeline_insights.py` (extend the import from the parser module): + +```python +from flowx.parser.pipeline_insights import validate_insights + + +def _inventory() -> dict: + """A minimal inventory dict in discover's serialized shape.""" + return { + "source_dir": "/tmp/adf", + "pipelines": [ + {"name": "factory_a", "activities": []}, + {"name": "factory_b", "activities": []}, + ], + "summary": {"pipeline_count": 2}, + "lineage": { + "control_edges": [ + { + "caller_pipeline": "factory_a", + "callee_pipeline": "factory_b", + "activity_name": "Run Ingestion Pipeline", + "wait_on_completion": True, + } + ], + "data_edges": [ + { + "dataset_name": "ds_orders", + "identity": "curated.orders", + "producer_pipeline": "factory_a", + "producer_activity": "Write Orders", + "consumer_pipeline": "factory_b", + "consumer_activity": "Read Orders", + "match_kind": "identity", + "match_key": "curated.orders", + } + ], + }, + } + + +def _good_insights() -> dict: + return { + "overview": "Two-stage ingestion then transform.", + "pipeline_insights": [ + {"pipeline": "factory_a", "intent": "Ingest", "databricks_pattern": "Autoloader"}, + {"pipeline": "factory_b", "intent": "Transform"}, + ], + "pipeline_relationships": [ + { + "from_pipeline": "factory_a", + "to_pipeline": "factory_b", + "lineage_edge": { + "edge_type": "control", + "edge_identity": "Run Ingestion Pipeline", + }, + "relationship_summary": "A invokes B", + "databricks_pattern": "run_job_task", + "risk_if_ignored": "ordering lost", + } + ], + } + + +def test_validator_accepts_good_insights(): + assert validate_insights(_good_insights(), _inventory()) == [] + + +def test_rejects_pipeline_not_in_inventory(): + raw = _good_insights() + raw["pipeline_insights"][0]["pipeline"] = "ghost_pipeline" + violations = validate_insights(raw, _inventory()) + assert violations + assert any("ghost_pipeline" in v for v in violations) + + +def test_rejects_relationship_endpoint_not_in_inventory(): + raw = _good_insights() + raw["pipeline_relationships"][0]["to_pipeline"] = "ghost_pipeline" + violations = validate_insights(raw, _inventory()) + assert any("ghost_pipeline" in v for v in violations) + + +def test_rejects_unresolvable_control_edge(): + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"]["edge_identity"] = "No Such Activity" + violations = validate_insights(raw, _inventory()) + assert any("No Such Activity" in v for v in violations) + + +def test_data_edge_binds_on_match_key(): + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"] = { + "edge_type": "data", + "edge_identity": "curated.orders", + } + assert validate_insights(raw, _inventory()) == [] + # a non-matching key is rejected + raw["pipeline_relationships"][0]["lineage_edge"]["edge_identity"] = "curated.missing" + assert validate_insights(raw, _inventory()) + + +def test_rejects_missing_required_field(): + # PipelineInsight missing 'pipeline' + raw = {"pipeline_insights": [{"intent": "x"}], "pipeline_relationships": []} + assert any("pipeline" in v for v in validate_insights(raw, _inventory())) + # PipelineRelationship missing 'lineage_edge' + raw2 = { + "pipeline_insights": [], + "pipeline_relationships": [{"from_pipeline": "factory_a", "to_pipeline": "factory_b"}], + } + assert any("lineage_edge" in v for v in validate_insights(raw2, _inventory())) + + +def test_rejects_unknown_field(): + raw = _good_insights() + raw["pipeline_insights"][0]["bogus_key"] = "x" + assert any("bogus_key" in v for v in validate_insights(raw, _inventory())) + + +def test_rejects_unknown_top_level_key(): + raw = _good_insights() + raw["surprise"] = 1 + assert any("surprise" in v for v in validate_insights(raw, _inventory())) +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k validate -v` +Expected: FAIL with `ModuleNotFoundError: No module named 'flowx.parser.pipeline_insights'` (plus the `data_edge`/`missing`/`unknown` tests erroring on import) + +- [ ] **Step 3: Write the validator** + +Create `src/flowx/parser/pipeline_insights.py`: + +```python +"""Validate and merge agent-authored insights into the discover inventory. + +The discover phase writes a pure ``metadata/inventory.json`` (pipelines, summary, +lineage). The agent then *authors* an ``insights`` object -- its judgment about +pipeline intent, Databricks patterns, and cross-pipeline relationships that +annotate the deterministic lineage edges. This module *enriches* the inventory: +it validates the authored JSON against the inventory (foreign keys to pipeline +names and lineage edges) and, only when clean, appends the single ``insights`` +key while re-serialising the rest byte-identically. + +There is no LLM here -- the tool only validates and merges. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +_INSIGHTS_TOP_KEYS = {"overview", "pipeline_insights", "pipeline_relationships"} +_INSIGHT_KEYS = { + "pipeline", + "pattern_name", + "intent", + "databricks_pattern", + "recommended_databricks_features", + "conversion_notes", +} +_RELATIONSHIP_KEYS = { + "from_pipeline", + "to_pipeline", + "lineage_edge", + "relationship_summary", + "databricks_pattern", + "risk_if_ignored", +} +_EDGE_KEYS = {"edge_type", "edge_identity"} + + +def _pipeline_names(inventory: dict) -> set[str]: + return {p.get("name") for p in inventory.get("pipelines", []) if isinstance(p, dict)} + + +def _control_edge_identities(inventory: dict) -> set[str]: + lineage = inventory.get("lineage") or {} + return {e.get("activity_name") for e in lineage.get("control_edges", []) if isinstance(e, dict)} + + +def _data_edge_identities(inventory: dict) -> set[str]: + lineage = inventory.get("lineage") or {} + return {e.get("match_key") for e in lineage.get("data_edges", []) if isinstance(e, dict)} + + +def validate_insights(raw: dict, inventory: dict) -> list[str]: + """Validate an authored insights dict against the inventory. + + Returns a list of human-readable violation strings; an empty list means the + insights are valid. All violations are collected (never fail-fast) so the + agent can fix every problem in one pass. + """ + violations: list[str] = [] + if not isinstance(raw, dict): + return [f"insights must be a JSON object, got {type(raw).__name__}"] + + for key in set(raw) - _INSIGHTS_TOP_KEYS: + violations.append(f"unknown top-level key: {key!r}") + + names = _pipeline_names(inventory) + control_ids = _control_edge_identities(inventory) + data_ids = _data_edge_identities(inventory) + + insights = raw.get("pipeline_insights", []) + if not isinstance(insights, list): + violations.append("'pipeline_insights' must be a list") + insights = [] + for i, item in enumerate(insights): + loc = f"pipeline_insights[{i}]" + if not isinstance(item, dict): + violations.append(f"{loc} must be an object") + continue + for key in set(item) - _INSIGHT_KEYS: + violations.append(f"{loc}: unknown field {key!r}") + name = item.get("pipeline") + if not name: + violations.append(f"{loc}: missing required field 'pipeline'") + elif name not in names: + violations.append(f"{loc}: pipeline {name!r} not in inventory") + + relationships = raw.get("pipeline_relationships", []) + if not isinstance(relationships, list): + violations.append("'pipeline_relationships' must be a list") + relationships = [] + for i, rel in enumerate(relationships): + loc = f"pipeline_relationships[{i}]" + if not isinstance(rel, dict): + violations.append(f"{loc} must be an object") + continue + for key in set(rel) - _RELATIONSHIP_KEYS: + violations.append(f"{loc}: unknown field {key!r}") + for endpoint in ("from_pipeline", "to_pipeline"): + value = rel.get(endpoint) + if not value: + violations.append(f"{loc}: missing required field {endpoint!r}") + elif value not in names: + violations.append(f"{loc}: {endpoint} {value!r} not in inventory") + violations.extend(_validate_edge(rel.get("lineage_edge"), loc, control_ids, data_ids)) + + return violations + + +def _validate_edge(edge: Any, loc: str, control_ids: set[str], data_ids: set[str]) -> list[str]: + """Validate one lineage_edge ref: shape + resolution to a real edge.""" + if edge is None: + return [f"{loc}: missing required field 'lineage_edge'"] + if not isinstance(edge, dict): + return [f"{loc}.lineage_edge must be an object"] + problems: list[str] = [] + for key in set(edge) - _EDGE_KEYS: + problems.append(f"{loc}.lineage_edge: unknown field {key!r}") + edge_type = edge.get("edge_type") + identity = edge.get("edge_identity") + if edge_type not in ("control", "data"): + problems.append(f"{loc}.lineage_edge: edge_type must be 'control' or 'data', got {edge_type!r}") + return problems + if not isinstance(identity, str) or not identity: + problems.append(f"{loc}.lineage_edge: edge_identity must be a non-empty string") + return problems + valid = control_ids if edge_type == "control" else data_ids + if identity not in valid: + problems.append( + f"{loc}.lineage_edge: {edge_type} edge {identity!r} does not resolve to any lineage edge" + ) + return problems +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k validate -v` then `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k "data_edge or missing or unknown" -v` +Expected: PASS (all validator + edge + field tests) + +- [ ] **Step 5: Commit** + +```bash +git add src/flowx/parser/pipeline_insights.py tests/unit/test_pipeline_insights.py +git commit -m "$(cat <<'EOF' +Add validate_insights: FK + lineage-edge validator + +Pure, violation-collecting validator. Checks pipeline-name FKs, resolves +each lineage_edge ref to a real control/data edge (activity_name / +match_key), and rejects unknown or missing fields. + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Task 3: `load_insights`, `merge_into_inventory`, `enrich_inventory` (orchestrator + byte-identical write) + +**Files:** +- Modify: `src/flowx/parser/pipeline_insights.py` +- Test: `tests/unit/test_pipeline_insights.py` + +**Interfaces:** +- Consumes: `validate_insights` (Task 2). Reads `/metadata/inventory.json`. +- Produces: + - `load_insights(*, insights: dict|None=None, insights_path: Path|None=None) -> dict` — returns the raw insights dict from exactly one source; raises `ValueError` if neither or both are given. + - `merge_into_inventory(inventory: dict, raw: dict) -> dict` — returns a new dict with one added `insights` key; does not mutate input; no I/O. + - `enrich_inventory(output_dir: Path, *, insights: dict|None=None, insights_path: Path|None=None) -> dict` — orchestrator returning `{"ok": bool, "violations": list[str], "pipeline_insights": int, "relationships": int}`. On violations, returns `ok=False` WITHOUT writing. On success, writes the merged inventory and returns `ok=True`. + +- [ ] **Step 1: Write the failing tests** + +Add to `tests/unit/test_pipeline_insights.py`: + +```python +import pytest + +from flowx.parser.pipeline_insights import ( + enrich_inventory, + load_insights, + merge_into_inventory, +) + + +def _write_inventory(tmp_path: Path, inventory: dict) -> Path: + """Write inventory.json exactly as discover does (indent=2, no trailing newline).""" + metadata = tmp_path / "metadata" + metadata.mkdir(parents=True, exist_ok=True) + path = metadata / "inventory.json" + path.write_text(json.dumps(inventory, indent=2), encoding="utf-8") + return path + + +def test_load_insights_requires_exactly_one_source(): + with pytest.raises(ValueError): + load_insights() + with pytest.raises(ValueError): + load_insights(insights={"a": 1}, insights_path=Path("/x")) + + +def test_load_insights_from_inline_dict(): + assert load_insights(insights={"overview": "x"}) == {"overview": "x"} + + +def test_load_insights_from_path(tmp_path: Path): + p = tmp_path / "ins.json" + p.write_text(json.dumps({"overview": "y"}), encoding="utf-8") + assert load_insights(insights_path=p) == {"overview": "y"} + + +def test_merge_into_inventory_adds_one_key_without_mutating(): + inv = {"pipelines": [], "summary": {}, "lineage": {}} + raw = {"overview": "z"} + merged = merge_into_inventory(inv, raw) + assert merged["insights"] == {"overview": "z"} + assert "insights" not in inv # input not mutated + assert set(merged) == {"pipelines", "summary", "lineage", "insights"} + + +def test_enrich_success_counts_and_writes(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + result = enrich_inventory(tmp_path, insights=_good_insights()) + assert result["ok"] is True + assert result["violations"] == [] + assert result["pipeline_insights"] == 2 + assert result["relationships"] == 1 + on_disk = json.loads((tmp_path / "metadata" / "inventory.json").read_text()) + assert on_disk["insights"]["overview"] == "Two-stage ingestion then transform." + + +def test_two_pass_deterministic_keys_byte_identical(tmp_path: Path): + path = _write_inventory(tmp_path, _inventory()) + before = path.read_text(encoding="utf-8") + enrich_inventory(tmp_path, insights=_good_insights()) + after = json.loads(path.read_text(encoding="utf-8")) + # every key except the added 'insights' is byte-identical to the pre-enrich file + after_without_insights = {k: v for k, v in after.items() if k != "insights"} + assert json.dumps(after_without_insights, indent=2) == before + + +def test_enrich_is_idempotent(tmp_path: Path): + path = _write_inventory(tmp_path, _inventory()) + enrich_inventory(tmp_path, insights=_good_insights()) + first = path.read_text(encoding="utf-8") + enrich_inventory(tmp_path, insights=_good_insights()) + second = path.read_text(encoding="utf-8") + assert first == second + + +def test_validation_failure_does_not_write(tmp_path: Path): + path = _write_inventory(tmp_path, _inventory()) + before = path.read_text(encoding="utf-8") + bad = _good_insights() + bad["pipeline_insights"][0]["pipeline"] = "ghost_pipeline" + result = enrich_inventory(tmp_path, insights=bad) + assert result["ok"] is False + assert result["violations"] + assert path.read_text(encoding="utf-8") == before # file untouched + + +def test_enrich_missing_inventory_raises(tmp_path: Path): + with pytest.raises(FileNotFoundError): + enrich_inventory(tmp_path, insights=_good_insights()) +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k "load_insights or merge_into or enrich or two_pass or idempotent or validation_failure" -v` +Expected: FAIL with `ImportError: cannot import name 'enrich_inventory'` + +- [ ] **Step 3: Add the orchestrator functions** + +Append to `src/flowx/parser/pipeline_insights.py`: + +```python +def load_insights(*, insights: dict | None = None, insights_path: Path | None = None) -> dict: + """Return the raw insights dict from exactly one source (inline or file). + + Raises: + ValueError: if neither or both sources are provided. + """ + if (insights is None) == (insights_path is None): + raise ValueError("provide exactly one of 'insights' (inline dict) or 'insights_path'") + if insights is not None: + return insights + return json.loads(Path(insights_path).read_text(encoding="utf-8")) + + +def merge_into_inventory(inventory: dict, raw: dict) -> dict: + """Return a new dict identical to *inventory* with one added ``insights`` key. + + Does not mutate the input. No I/O. + """ + merged = dict(inventory) + merged["insights"] = raw + return merged + + +def enrich_inventory( + output_dir: Path, + *, + insights: dict | None = None, + insights_path: Path | None = None, +) -> dict: + """Validate authored insights against the inventory, then merge on success. + + Reads ``/metadata/inventory.json``, validates the authored + insights, and -- only when there are no violations -- writes the merged + inventory back byte-identically (adding just the ``insights`` key). + + Returns ``{"ok", "violations", "pipeline_insights", "relationships"}``. + On violations, ``ok`` is False and the file is left untouched. + + Raises: + FileNotFoundError: when ``inventory.json`` does not exist. + """ + inventory_path = Path(output_dir) / "metadata" / "inventory.json" + if not inventory_path.exists(): + raise FileNotFoundError(f"No inventory.json under {inventory_path.parent}; run discover first.") + inventory = json.loads(inventory_path.read_text(encoding="utf-8")) + + raw = load_insights(insights=insights, insights_path=insights_path) + violations = validate_insights(raw, inventory) + if violations: + return {"ok": False, "violations": violations, "pipeline_insights": 0, "relationships": 0} + + merged = merge_into_inventory(inventory, raw) + inventory_path.write_text(json.dumps(merged, indent=2), encoding="utf-8") + return { + "ok": True, + "violations": [], + "pipeline_insights": len(raw.get("pipeline_insights", [])), + "relationships": len(raw.get("pipeline_relationships", [])), + } +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -v` +Expected: PASS (all tests in the file) + +- [ ] **Step 5: Commit** + +```bash +git add src/flowx/parser/pipeline_insights.py tests/unit/test_pipeline_insights.py +git commit -m "$(cat <<'EOF' +Add enrich_inventory: validate-then-append two-pass write + +load_insights (inline|path), merge_into_inventory (pure), and +enrich_inventory (orchestrator). Byte-identical re-serialize adds only +the 'insights' key; validation runs before any write, so a rejected +payload leaves inventory.json untouched. Idempotent. + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Task 4: Edge-binding tests on the real nested fixture + +This task hardens the validator against a *genuine* inventory built from the shipped fixture (not a hand-faked dict), proving control-edge identities resolve on real `activity_name` values. + +**Files:** +- Test: `tests/unit/test_pipeline_insights.py` + +**Interfaces:** +- Consumes: `load_adf_definitions` (`flowx.parser.adf_loader`), `build_inventory` (`flowx.parser.adf_loader`, attaches lineage at line 247), `_inventory_to_dict` (`flowx.parser.adf_loader`), and `validate_insights` (Task 2). The fixture `pipeline_execute_pipeline_nested.json` yields control edges with `activity_name` values `"Run Ingestion Pipeline"`, `"Run Transform Pipeline"`, `"Run Cleanup Pipeline"` and callees `pipeline_copy_sql_to_delta`, `pipeline_notebook_with_params`, `pipeline_delete_recursive`. + +- [ ] **Step 1: Write the failing test** + +Add to `tests/unit/test_pipeline_insights.py`: + +```python +from flowx.parser.adf_loader import ( + _inventory_to_dict, + build_inventory, + load_adf_definitions, +) + + +def _real_inventory(fixtures_dir) -> dict: + """Build a real inventory dict (with lineage) from the shipped fixtures.""" + definitions = load_adf_definitions(fixtures_dir) + inventory = build_inventory(definitions) + return _inventory_to_dict(inventory, str(fixtures_dir)) + + +def test_control_edge_binding_matches_and_rejects(fixtures_dir): + inventory = _real_inventory(fixtures_dir) + good = { + "pipeline_insights": [], + "pipeline_relationships": [ + { + "from_pipeline": "pipeline_execute_pipeline_nested", + "to_pipeline": "pipeline_copy_sql_to_delta", + "lineage_edge": { + "edge_type": "control", + "edge_identity": "Run Ingestion Pipeline", + }, + } + ], + } + assert validate_insights(good, inventory) == [] + + bad = json.loads(json.dumps(good)) + bad["pipeline_relationships"][0]["lineage_edge"]["edge_identity"] = "No Such Activity" + assert validate_insights(bad, inventory) + + +def test_real_inventory_enrich_round_trip(fixtures_dir, tmp_path: Path): + inventory = _real_inventory(fixtures_dir) + metadata = tmp_path / "metadata" + metadata.mkdir(parents=True) + (metadata / "inventory.json").write_text(json.dumps(inventory, indent=2), encoding="utf-8") + result = enrich_inventory( + tmp_path, + insights={ + "overview": "orchestrated ingest/transform/cleanup", + "pipeline_insights": [{"pipeline": "pipeline_execute_pipeline_nested", "intent": "orchestrate"}], + "pipeline_relationships": [], + }, + ) + assert result["ok"] is True + on_disk = json.loads((metadata / "inventory.json").read_text()) + assert on_disk["insights"]["pipeline_insights"][0]["pipeline"] == "pipeline_execute_pipeline_nested" +``` + +- [ ] **Step 2: Run test to verify it passes (validator already handles this)** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k "control_edge_binding or real_inventory" -v` +Expected: PASS — the validator from Task 2 already resolves control edges by `activity_name`. If any test fails, fix `validate_insights`, not the test. (This task is a regression guard against a real inventory, so no new production code is expected.) + +- [ ] **Step 3: Commit** + +```bash +git add tests/unit/test_pipeline_insights.py +git commit -m "$(cat <<'EOF' +Test insights validation against a real fixture-built inventory + +Builds a genuine inventory (with lineage) from the shipped nested +ExecutePipeline fixture and asserts control-edge identities resolve on +real activity_name values. + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Task 5: The `enrich` adapter subcommand + +**Files:** +- Modify: `src/flowx/adapter/__main__.py` (add subparser in `_build_parser` after the `record` block ~line 362; add `_run_enrich` handler after `_run_record_results` ~line 116; add dispatch in `main` after line 88) +- Test: `tests/unit/test_pipeline_insights.py` + +**Interfaces:** +- Consumes: `enrich_inventory` (Task 3). +- Produces: CLI `python -m flowx.adapter enrich --output-dir

[--insights-path ] [--insights ]`. Returns exit code 0 on success, 1 on any failure (missing inventory, bad/absent/both payload sources, validation violations). Exposed via `adapter.__main__.main(argv)`. + +- [ ] **Step 1: Write the failing test** + +Add to `tests/unit/test_pipeline_insights.py`: + +```python +from flowx.adapter.__main__ import main as adapter_cli_main + + +def test_adapter_enrich_success(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + ins = tmp_path / "insights.json" + ins.write_text(json.dumps(_good_insights()), encoding="utf-8") + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path), "--insights-path", str(ins)]) + assert code == 0 + on_disk = json.loads((tmp_path / "metadata" / "inventory.json").read_text()) + assert "insights" in on_disk + + +def test_adapter_enrich_validation_failure_returns_1(tmp_path: Path): + path = _write_inventory(tmp_path, _inventory()) + before = path.read_text(encoding="utf-8") + bad = _good_insights() + bad["pipeline_insights"][0]["pipeline"] = "ghost_pipeline" + ins = tmp_path / "insights.json" + ins.write_text(json.dumps(bad), encoding="utf-8") + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path), "--insights-path", str(ins)]) + assert code == 1 + assert path.read_text(encoding="utf-8") == before # untouched + + +def test_adapter_enrich_missing_inventory_returns_1(tmp_path: Path): + ins = tmp_path / "insights.json" + ins.write_text(json.dumps(_good_insights()), encoding="utf-8") + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path), "--insights-path", str(ins)]) + assert code == 1 + + +def test_adapter_enrich_inline_json_string(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + code = adapter_cli_main( + ["enrich", "--output-dir", str(tmp_path), "--insights", json.dumps(_good_insights())] + ) + assert code == 0 + assert "insights" in json.loads((tmp_path / "metadata" / "inventory.json").read_text()) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k adapter_enrich -v` +Expected: FAIL — argparse exits with code 2 ("invalid choice: 'enrich'") since the subcommand does not exist yet. + +- [ ] **Step 3: Add dispatch in `main()`** + +In `src/flowx/adapter/__main__.py`, add after line 88 (`return _run_record_results(args)`): + +```python + if args.command == "enrich": + return _run_enrich(args) +``` + +- [ ] **Step 4: Add the `_run_enrich` handler** + +Add after `_run_record_results` (after line 115), following its structure: + +```python +def _run_enrich(args: argparse.Namespace) -> int: + """Implements ``enrich``: validate + merge agent-authored insights into inventory.json. + + Returns 0 on success, 1 on any failure (missing inventory, unreadable/absent/ + both payload sources, or validation violations). + """ + from flowx.parser.pipeline_insights import enrich_inventory + + metadata_dir = args.output_dir / "metadata" + if not (metadata_dir / "inventory.json").exists(): + print(f"No inventory.json under {metadata_dir}; run the discover phase first.", file=sys.stderr) + return 1 + + inline: dict[str, Any] | None = None + if args.insights is not None: + try: + inline = json.loads(args.insights) + except json.JSONDecodeError as error: + print(f"Invalid --insights JSON: {error}", file=sys.stderr) + return 1 + if (inline is None) == (args.insights_path is None): + print("Provide exactly one of --insights (inline JSON) or --insights-path.", file=sys.stderr) + return 1 + + try: + result = enrich_inventory(args.output_dir, insights=inline, insights_path=args.insights_path) + except (OSError, ValueError, json.JSONDecodeError) as error: + print(f"Failed to enrich inventory: {error}", file=sys.stderr) + return 1 + + if not result["ok"]: + for violation in result["violations"]: + print(f" - {violation}", file=sys.stderr) + print( + f"Insights validation failed ({len(result['violations'])} violation(s)); " + "inventory not modified.", + file=sys.stderr, + ) + return 1 + print( + f"Enriched inventory: {result['pipeline_insights']} pipeline insight(s), " + f"{result['relationships']} relationship(s)." + ) + return 0 +``` + +- [ ] **Step 5: Add the `enrich` subparser** + +In `_build_parser`, add after the `record` subparser block (after line 362, before the `dashboard` parser): + +```python + enrich = subparsers.add_parser( + "enrich", + help="Validate and merge agent-authored insights into metadata/inventory.json.", + ) + enrich.add_argument( + "--output-dir", + type=Path, + required=True, + help="Migration output directory (reads/writes metadata/inventory.json).", + ) + enrich.add_argument( + "--insights-path", + type=Path, + default=None, + help="Path to a JSON file holding the insights object.", + ) + enrich.add_argument( + "--insights", + type=str, + default=None, + help="Insights object as an inline JSON string (convenience for direct CLI use).", + ) +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k adapter_enrich -v` +Expected: PASS + +- [ ] **Step 7: Commit** + +```bash +git add src/flowx/adapter/__main__.py tests/unit/test_pipeline_insights.py +git commit -m "$(cat <<'EOF' +Add 'enrich' adapter subcommand + +python -m flowx.adapter enrich --output-dir (--insights-path +| --insights ). Validates + merges insights; returns 1 (inventory +untouched) on missing inventory, bad payload, or validation violations. + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Task 6: MCP `enrich` command + `runner.materialize_json` + +**Files:** +- Modify: `src/flowx/mcp/runner.py` (add `materialize_json` near `materialize_adf_definitions` ~line 156) +- Modify: `src/flowx/mcp/server.py` (add `_cmd_enrich` before `_COMMANDS` ~line 363; register `"enrich"` in `_COMMANDS` ~line 377; add a docstring bullet ~line 426) +- Test: `tests/unit/test_pipeline_insights.py` + +**Interfaces:** +- Consumes: `runner.run_adapter`, `runner.summarize_inventory`, `runner.materialize_json` (new), `runner.cleanup_materialized`, `server._phase_result`. +- Produces: `runner.materialize_json(obj: Any) -> str` (writes a temp JSON file, returns its path; cleaned up by `cleanup_materialized`). `server._cmd_enrich(p)` accepting `output_dir`, `insights` (inline dict) or `insights_path`. + +- [ ] **Step 1: Write the failing test** + +Add to `tests/unit/test_pipeline_insights.py`: + +```python +from flowx.mcp import runner as mcp_runner +from flowx.mcp.server import _cmd_enrich + + +def test_materialize_json_round_trips(tmp_path: Path): + path = mcp_runner.materialize_json({"overview": "x"}) + try: + assert json.loads(Path(path).read_text()) == {"overview": "x"} + finally: + mcp_runner.cleanup_materialized(path) + assert not Path(path).exists() + + +def test_cmd_enrich_requires_a_payload(): + result = _cmd_enrich({"output_dir": "./flowx_output"}) + assert result["ok"] is False + assert "insights" in result["error"] + + +def test_cmd_enrich_inline_dict_success(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + result = _cmd_enrich({"output_dir": str(tmp_path), "insights": _good_insights()}) + assert result["ok"] is True + assert "insights" in json.loads((tmp_path / "metadata" / "inventory.json").read_text()) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k "materialize_json or cmd_enrich" -v` +Expected: FAIL with `ImportError: cannot import name '_cmd_enrich'` / `AttributeError: module ... has no attribute 'materialize_json'` + +- [ ] **Step 3: Add `materialize_json` to the runner** + +In `src/flowx/mcp/runner.py`, add after `materialize_adf_definitions` (after line 204): + +```python +def materialize_json(obj: Any) -> str: + """Write a JSON-serialisable object to a temp file and return its path. + + Lets the MCP server pass an inline ``insights`` dict to the adapter's + ``enrich`` subcommand (which reads from ``--insights-path``). Clean up with + :func:`cleanup_materialized`. + """ + fd, path = tempfile.mkstemp(prefix="flowx-insights-", suffix=".json") + with os.fdopen(fd, "w", encoding="utf-8") as handle: + json.dump(obj, handle) + return path +``` + +(`os`, `json`, `tempfile` are already imported at the top of runner.py — lines 12-18.) + +- [ ] **Step 4: Add `_cmd_enrich` to the server** + +In `src/flowx/mcp/server.py`, add before `_COMMANDS` (before line 365): + +```python +def _cmd_enrich(p: dict[str, Any]) -> dict[str, Any]: + output_dir = p.get("output_dir", "./flowx_output") + insights = p.get("insights") + insights_path = p.get("insights_path") + if insights is None and not insights_path: + return {"ok": False, "error": "Provide 'insights' (inline dict) or 'insights_path'."} + tmp: str | None = None + try: + if insights is not None: + tmp = runner.materialize_json(insights) + insights_path = tmp + args: list[Any] = ["enrich", "--output-dir", output_dir, "--insights-path", insights_path] + result = runner.run_adapter(args) + out = Path(output_dir) + return _phase_result(result, out, inventory=runner.summarize_inventory(out)) + finally: + if tmp: + runner.cleanup_materialized(tmp) +``` + +- [ ] **Step 5: Register the command and document it** + +In `src/flowx/mcp/server.py`, add to the `_COMMANDS` dict (after line 368, `"convert": _cmd_convert,` grouping — place it right after `"discover": _cmd_discover,`): + +```python + "enrich": _cmd_enrich, +``` + +Then add a bullet to the `flowx` tool docstring after the "discover" bullet (after line 408): + +```python + - "enrich": output_dir(req), one of insights(inline dict) | insights_path — validate + merge + agent-authored insights into metadata/inventory.json (returns {ok:false, ...} without writing + on validation failure). +``` + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_pipeline_insights.py -k "materialize_json or cmd_enrich" -v` +Expected: PASS + +- [ ] **Step 7: Commit** + +```bash +git add src/flowx/mcp/runner.py src/flowx/mcp/server.py tests/unit/test_pipeline_insights.py +git commit -m "$(cat <<'EOF' +Add 'enrich' MCP command + runner.materialize_json + +_cmd_enrich materializes an inline insights dict to a temp file and drives +the adapter enrich subcommand; runner.materialize_json parallels +materialize_adf_definitions. Registered in _COMMANDS and documented. + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Task 7: Optional `has_insights` reporting column + +Optional per the spec — include only if it stays trivial and does not perturb existing coverage tests. + +**Files:** +- Modify: `src/flowx/reporting/coverage.py` (`COVERAGE_METRIC_COLUMNS` ~line 22; `build_coverage_rows` ~line 57) +- Test: `tests/unit/test_reporting_coverage.py` + +**Interfaces:** +- Consumes: the inventory dict already loaded in `build_coverage_rows` at line 73. +- Produces: a `has_insights` boolean field on each coverage row, gated on the top-level `insights` key. Non-enriched inventories yield `False`; the column is identical for every pipeline in a run (it is a factory-level marker). + +- [ ] **Step 1: Write the failing test** + +Add to `tests/unit/test_reporting_coverage.py` (the module already imports `json`, `Path`, and `build_coverage_rows`, and defines the `_write_metadata(tmp_path) -> Path` helper that writes `metadata/inventory.json` without an `insights` key): + +```python +def test_has_insights_column_reflects_insights_key(tmp_path: Path): + md = _write_metadata(tmp_path) # writes inventory.json with no insights key + rows = build_coverage_rows(md) + assert all(row["has_insights"] is False for row in rows) + + inv_path = md / "inventory.json" + inv = json.loads(inv_path.read_text()) + inv["insights"] = {"overview": "x", "pipeline_insights": [], "pipeline_relationships": []} + inv_path.write_text(json.dumps(inv), encoding="utf-8") + rows2 = build_coverage_rows(md) + assert all(row["has_insights"] is True for row in rows2) +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_reporting_coverage.py::test_has_insights_column_reflects_insights_key -v` +Expected: FAIL with `KeyError: 'has_insights'` + +- [ ] **Step 3: Add the column** + +In `src/flowx/reporting/coverage.py`, append `"has_insights"` to `COVERAGE_METRIC_COLUMNS` (after `"complexity_size"` at line 36): + +```python + "complexity_size", + "has_insights", +``` + +Then, in `build_coverage_rows`, compute the factory-level marker once just before the `rows: list[dict[str, Any]] = []` line (line 82): + +```python + has_insights = "insights" in inventory +``` + +and add `"has_insights": has_insights,` as the last entry of the per-pipeline dict appended in the loop, immediately after `"complexity_size": csv_row.get("complexity_size", "") or "",` (line 113): + +```python + "complexity_size": csv_row.get("complexity_size", "") or "", + "has_insights": has_insights, +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `PYTHONPATH=src uv run pytest tests/unit/test_reporting_coverage.py -v` +Expected: PASS. The two existing coverage tests (`test_build_coverage_rows_joins_inventory_and_csv`, `test_build_coverage_rows_full_coverage_and_missing_csv`) assert individual named columns, not an exact column count or the full `COVERAGE_METRIC_COLUMNS` tuple, so adding a column does not break them. + +- [ ] **Step 5: Commit** + +```bash +git add src/flowx/reporting/coverage.py tests/unit/test_reporting_coverage.py +git commit -m "$(cat <<'EOF' +Add has_insights coverage column (gated on insights key) + +Factory-level marker in the coverage rows; False for non-enriched +inventories so existing runs are unaffected. + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Task 8: Discover skill — Step 5 (author → enrich) + summary rewording + +**Files:** +- Modify: `skills/flowx-discover/SKILL.md` + +**Interfaces:** +- Consumes: the `enrich` MCP command (Task 6) and the `enrich` adapter subcommand (Task 5). Documentation only — no test cycle; verified manually in Task 9. + +- [ ] **Step 1: Insert the new Step 5 (author → enrich)** + +In `skills/flowx-discover/SKILL.md`, insert this new section between the current Step 4b (ends at line 212) and the current `### Step 5 — Present the summary` (line 214): + +````markdown +### Step 5 — Author and merge agentic insights + +The deterministic inventory records *what* each pipeline contains; it cannot +record *what the factory is trying to do* or *how the pipelines relate as a +system*. Author that judgment now and merge it into `inventory.json` under an +`insights` key. This always runs. + +1. **Read** the just-written `inventory.json` (`pipelines`, `lineage`, `summary`) + and `profile_report.csv`. +2. **Author** an `insights` object: + - `overview` — the whole factory as one system, plus the single biggest + migration steer. + - `pipeline_insights[]` — **sparse**; per pipeline an `intent` and a + `databricks_pattern` (optionally `pattern_name`, + `recommended_databricks_features`, `conversion_notes`). Omit pipelines with + nothing worth saying. + - `pipeline_relationships[]` — annotate the lineage edges the discover phase + already found. Each relationship carries a `lineage_edge` + (`{edge_type, edge_identity}`) plus `relationship_summary`, + `databricks_pattern`, and `risk_if_ignored`. For a **control** edge, + `edge_identity` is the edge's `activity_name`; for a **data** edge, it is + the edge's `match_key` — copied **verbatim** from a real edge in + `lineage`. + - **Authoring rules:** reference only pipeline names that exist in the + inventory; every `lineage_edge` must echo a real edge; do not invent + relationships the lineage did not find (annotate, don't rediscover). +3. **Enrich** — merge the object in: + + - **MCP tool path** (inline dict; the only path in Genie Code): + + ``` + flowx(command="enrich", parameters={ + "output_dir": "", + "insights": { ...authored object... }}) + ``` + + - **venv CLI fallback** (write the object to a JSON file first): + + ```bash + "$PY" -m flowx.adapter enrich --output-dir --insights-path + ``` + +4. **On `ok:false`** the tool did **not** write the file: read `violations`, fix + the offending pipeline name / lineage edge / field, and call `enrich` again. + On `ok:true` the `insights` key is now merged into `inventory.json`. +```` + +- [ ] **Step 2: Renumber the existing reporting steps** + +Renumber the four existing headings that follow (they currently read Steps 5–8): +- `### Step 5 — Present the summary` → `### Step 6 — Present the summary` +- `### Step 6 — Detail agentic activities` → `### Step 7 — Detail agentic activities` +- `### Step 7 — Warn about unsupported activities` → `### Step 8 — Warn about unsupported activities` +- `### Step 8 — Confirm output location` → `### Step 9 — Confirm output location` + +- [ ] **Step 3: Reword the summary step to surface insights** + +In the renumbered `### Step 6 — Present the summary`, after the existing summary code block (the `Coverage: 95.7%` block ending at line 230), append: + +````markdown +Then surface the authored judgment so the user sees *what the factory does*, not +just coverage numbers: print the factory `overview`, and for each +`pipeline_insights` entry its `pattern_name` / `intent` and recommended +Databricks pattern. Read these back from the enriched `inventory.json`. +```` + +- [ ] **Step 4: Add a "Future considerations" note** + +At the end of the file (after the `## Output Artifacts` table, line 273), append: + +````markdown +## Future considerations + +Insights are currently authored in a single pass over the whole factory. For very +large factories, revisit partitioning the authoring across subagents keyed on +lineage clusters (the connected components of the combined control/data-edge +graph), so each subagent reasons about one coherent subsystem. Out of scope for +now — always enrich in one pass. +```` + +- [ ] **Step 5: Verify the skill reads coherently** + +Run: `PYTHONPATH=src uv run pytest tests/unit -q` (sanity — no test touches the skill, but confirm nothing regressed) +Read the edited `skills/flowx-discover/SKILL.md` end-to-end and confirm: steps are numbered 1→9 with no duplicates or gaps, the new Step 5 sits between 4b and the summary, and both tool paths are shown. + +- [ ] **Step 6: Commit** + +```bash +git add skills/flowx-discover/SKILL.md +git commit -m "$(cat <<'EOF' +Add discover Step 5: author + enrich agentic insights + +New always-on author->enrich loop (both MCP and venv-CLI paths), summary +step reworded to surface factory overview + per-pipeline intent/pattern, +and a future-considerations note on partitioning authoring by lineage +cluster. Renumbers the reporting steps to 6-9. + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Task 9: End-to-end verification + format/lint gate + +**Files:** none (verification only) + +**Interfaces:** exercises the shipped `discover` → `enrich` path end-to-end over the repo fixtures. + +- [ ] **Step 1: Run the full unit suite** + +Run: `PYTHONPATH=src uv run pytest tests/unit -v` +Expected: PASS (all tests, including the new `test_pipeline_insights.py` and the coverage test). + +- [ ] **Step 2: Format + lint (ruff + mypy)** + +Run: `make fmt` +Expected: ruff formats/fixes cleanly and `mypy src/flowx/` reports no errors. Fix any type errors (e.g. add annotations) and re-run until clean. + +- [ ] **Step 3: End-to-end discover → enrich over fixtures** + +Run (real CLI, real fixture inventory, temp output dir): + +```bash +cd /Users/matthew.moorcroft/Code/work/flowx-worktrees/feat-discover-insights +OUT="$(mktemp -d)" +PYTHONPATH=src uv run python -m flowx.adapter discover \ + --adf-source-path tests/resources/json --output-dir "$OUT" +# capture the deterministic portion before enrich +PYTHONPATH=src uv run python -c "import json,sys; d=json.load(open(sys.argv[1])); print(json.dumps({k:v for k,v in d.items() if k!='insights'}, indent=2))" "$OUT/metadata/inventory.json" > /tmp/before.json +# author a tiny valid insights object referencing a real pipeline + control edge, then enrich +PYTHONPATH=src uv run python -m flowx.adapter enrich --output-dir "$OUT" --insights '{"overview":"fixtures","pipeline_insights":[{"pipeline":"pipeline_execute_pipeline_nested","intent":"orchestrate"}],"pipeline_relationships":[{"from_pipeline":"pipeline_execute_pipeline_nested","to_pipeline":"pipeline_copy_sql_to_delta","lineage_edge":{"edge_type":"control","edge_identity":"Run Ingestion Pipeline"}}]}' +# confirm insights present AND deterministic portion byte-identical +PYTHONPATH=src uv run python -c "import json,sys; d=json.load(open(sys.argv[1])); assert 'insights' in d; print('insights present:', bool(d['insights']))" "$OUT/metadata/inventory.json" +PYTHONPATH=src uv run python -c "import json,sys; d=json.load(open(sys.argv[1])); print(json.dumps({k:v for k,v in d.items() if k!='insights'}, indent=2))" "$OUT/metadata/inventory.json" > /tmp/after.json +diff /tmp/before.json /tmp/after.json && echo "DETERMINISTIC PORTION BYTE-IDENTICAL" +rm -rf "$OUT" /tmp/before.json /tmp/after.json +``` + +Expected: the enrich prints `Enriched inventory: 1 pipeline insight(s), 1 relationship(s).`, `insights present: True`, and `diff` reports no differences (`DETERMINISTIC PORTION BYTE-IDENTICAL`). + +- [ ] **Step 4: Confidentiality grep** + +Run: `git grep -nE "a customer factory|a large factory|engagementID|engagementDBVersions|etl-parameters" -- ':!docs/superpowers/specs/'` +Expected: no output (zero hits outside the spec). + +- [ ] **Step 5: Final commit (only if Steps 2/3 required fixups)** + +```bash +git add -A +git commit -m "$(cat <<'EOF' +Format/lint fixups for discover insights + +Co-authored-by: Isaac +EOF +)" +``` + +--- + +## Self-Review + +**1. Spec coverage** (checked against `docs/superpowers/specs/2026-07-23-discover-insights-design.md`): +- §4 data models → Task 1. §5 parser (`load_insights`/`validate_insights`/`merge_into_inventory`/`enrich_inventory`) → Tasks 2–3. §6a adapter subcommand → Task 5. §6b MCP command + `materialize_json` → Task 6. §7 skill Step 5 + summary + future note → Task 8. §8 test matrix → Tasks 2–6 (each issue invariant maps to a named test); edge-binding on the real fixture → Task 4; optional reporting column → Task 7; end-to-end → Task 9. §3 resolved decisions (data-edge on `match_key`, no `schema_version`, always-enrich, validate-before-write, byte-identical) are enforced in Global Constraints + Tasks 3/8. §10 confidentiality → Global Constraints + Task 9 Step 4. +- Every §8 test row has a home: `test_validator_accepts_good_insights` (T2), `test_rejects_pipeline_not_in_inventory` (T2), `test_rejects_unresolvable_*_edge` (T2), `test_rejects_missing_required_field` (T2), `test_rejects_unknown_field` (T2), `test_control_edge_binding_matches_and_rejects` (T4), `test_data_edge_binds_on_match_key` (T2), `test_two_pass_deterministic_keys_byte_identical` (T3), `test_enrich_is_idempotent` (T3), `test_validation_failure_does_not_write` (T3). + +**2. Placeholder scan:** No "TBD"/"handle edge cases"/"similar to Task N" — every code step shows complete code. Task 7 anchors its edits to exact line numbers and the real `_write_metadata` helper (verified present in `test_reporting_coverage.py`), so no "inspect at implementation time" hand-waving remains. + +**3. Type consistency:** `enrich_inventory(output_dir, *, insights=None, insights_path=None) -> dict` used identically in Tasks 3, 5, 6. Return keys `ok`/`violations`/`pipeline_insights`/`relationships` consistent across Task 3 (definition), Task 5 (`_run_enrich` reads `result["ok"]`, `result["violations"]`, `result["pipeline_insights"]`, `result["relationships"]`). `validate_insights(raw, inventory) -> list[str]` consistent across Tasks 2, 3, 4. `materialize_json(obj) -> str` / `cleanup_materialized(path)` consistent in Task 6. `LineageEdgeRef`/`edge_type`/`edge_identity` naming consistent between Task 1 models and the validator's `_EDGE_KEYS` in Task 2. diff --git a/docs/superpowers/specs/2026-07-23-discover-insights-design.md b/docs/superpowers/specs/2026-07-23-discover-insights-design.md new file mode 100644 index 0000000..e3a23e0 --- /dev/null +++ b/docs/superpowers/specs/2026-07-23-discover-insights-design.md @@ -0,0 +1,449 @@ +# Design — Agentic `insights` in `inventory.json` (discover phase) + +**Issue:** databricks-field-eng/flowx #11 — *[FEATURE]: Agentic insights in inventory.json (discover phase)* +**Depends on:** #9 (deterministic lineage), implemented in PR #18 on branch `feat/discover-lineage`. +**Branch:** `feat/discover-insights`, cut from `feat/discover-lineage`. PR bases on `feat/discover-lineage`; retarget to `main` after #18 merges. +**Status:** Design approved; ready for implementation planning. + +--- + +## 1. Summary + +Add an agent-authored **`insights`** key to `metadata/inventory.json` during the **discover** phase. It gives +the later **convert** phase two things it cannot derive deterministically: + +1. **What each pipeline is trying to achieve** and the Databricks pattern that maps to it. +2. **How pipelines relate across the whole factory**, by *annotating* #9's deterministic `lineage` edges. + +This issue only **produces** the block. A separate follow-up wires `convert` to consume it (mirrors the +#9 produce → consume split). + +**Design principle — every edge is accountable.** The inventory + `lineage` are the source of truth for +deterministic *facts*. The agent adds *judgment*, referencing pipelines and edges by their existing identifiers. +Most insight is therefore cheap to validate: named pipelines must exist; annotated (`control`/`data`) edges must +resolve to a real lineage edge — *annotate, don't rediscover*. The one exception is genuine coupling the +deterministic layer structurally cannot see (data flow inside notebook code, external triggers): the agent may +assert an `inferred` edge, but it is held to the same accountability by a different key — it must cite non-empty +`evidence` and a `confidence` level, and is clearly distinguished from proven lineage. Scope is deliberately +**factory / pipeline / relationship** level — **no per-activity fields** (convert already handles that level +well). + +--- + +## 2. The two steps: "author" → "enrich" + +Step 5 of the discover skill is a small loop with a clear division of labour. There is **no LLM inside the +tool** — the tool only validates and merges. + +| Step | Who | What | +|---|---|---| +| discover (pass 1) | deterministic | Writes pure `inventory.json` (`pipelines`, `summary`, `lineage`). No `insights`. | +| **author** | the agent | Reads inventory + lineage + profile; writes the `insights` JSON (intent, patterns, relationships). The LLM-judgment part. | +| **enrich** | the tool | `flowx(command="enrich")`: validates the authored JSON against the inventory, then merges. Pure code. | +| enrich (pass 2) | deterministic | Appends **only** the `insights` key; re-serializes the deterministic portion **byte-identical**. Idempotent. | + +**Author happens first; enrich happens second.** The tool always validates whatever it is handed. The decision +of *whether* Step 5 runs lives in the **skill**, never in the tool — and per the decisions below, it **always +runs**. + +### Call shape + +```python +flowx(command="enrich", parameters={ + "output_dir": "./flowx_output", # locates metadata/inventory.json + "insights": { ... }, # inline dict (hosted / Genie path) — OR — + "insights_path": "/path/insights.json", # a readable path (local / CLI path) +}) +# Returns {ok, process, ...} + a merge summary. +# On ANY validation failure → {ok: false, violations: [...]} and DOES NOT WRITE the file. +``` + +--- + +## 3. Resolved decisions + +These supersede the issue's open questions and loose wording. + +- **`edge_identity` grammar (resolves the issue's open question).** + - `edge_type: "control"` → `edge_identity` = the `ControlEdge.activity_name` (the ExecutePipeline activity name). + - `edge_type: "data"` → `edge_identity` = the `DataEdge.match_key` — **not** "shared table/path". #9's data + edges carry a two-tier join (`match_kind` = `identity` | `expression`); `match_key` is the canonical join + value and `identity` is `null` for expression edges, so `match_key` is the only stable key. **Validation + matches on `match_key`.** + - `edge_type: "inferred"` → an agent-asserted coupling the deterministic layer never found, so there is **no** + lineage edge to resolve against. `edge_identity` is an agent-authored descriptor of the coupling (e.g. the + shared table/asset). Because it cannot be checked against a fact, the edge **must** instead carry a non-empty + `evidence` string and a `confidence` ∈ {`high`, `medium`, `low`}; validation enforces those and skips lineage + resolution. `evidence` / `confidence` are inferred-only — supplying them on a `control`/`data` edge is a + violation. +- **Why the `inferred` tier (generic data-flow capture).** The annotate-only edges (`control`/`data`) can only + describe couplings the deterministic layer surfaced. But a notebook-centric factory expresses its real data + flow *inside* notebook code (one notebook writes a table another reads), which ADF never names, so #9 finds + **zero** data edges there. The `inferred` tier gives the agent a structured, accountable place to record that + coupling instead of burying it in prose. It is deliberately **pattern-agnostic**: it does not encode *why* the + deterministic layer missed the edge (notebook I/O, external trigger, message queue, an ADF pattern we have not + seen), so it generalises. The evidence+confidence requirement preserves the invariant's spirit — every edge is + accountable to something (a proven fact for annotations, stated evidence for inferences) and an inference can + never masquerade as proven lineage. +- **No new inventory fields; ARM is the deep-dive source.** The agent must characterize data flow, which for + notebook-centric factories means reading what activities actually do. Rather than lift selected `typeProperties` + (e.g. `notebookPath`) into `inventory.json` — a treadmill that would repeat for every known and unknown activity + type and defeat the "generic" goal — Step 5 sends the agent to the verbatim `metadata/*.arm.json`, which already + contains everything for every pattern. The inventory stays the lean deterministic skeleton. +- **ARM files are addressed by glob-and-match, never a constructed name.** `write_pipeline_arm` emits one + `.arm.json` per pipeline via `_sanitize_filename` (a lossy slug: `[^0-9A-Za-z._-]+ → _`). A + filename therefore cannot be reliably reconstructed from a pipeline name (spaces/parens/unicode are mangled; + distinct names can collide to one stem). Step 5 instructs the agent to **glob `metadata/*.arm.json` and match on + the top-level `"name"` field inside each file**, not to build `.arm.json`. There is no single fixed + filename. Each file is a **flat single-pipeline object** (`{"name", "properties": {"activities": [...]}}`) — not + a multi-resource ARM envelope, so there is no `resources[]` array and no top-level `type`; activities live under + `properties.activities` (recurse nested `ForEach`/`If`/`Switch`). Step 5's wording states this shape explicitly + (a clean-room run misparsed an assumed `resources[]` envelope, so the shape is called out to prevent it). +- **Step 5 authoring guidance is criteria-based, not count-based (validated by a clean-room run).** A no-context + subagent run on a 327-pipeline factory surfaced two instruction gaps, fixed per prompt-engineering best practice + (Anthropic prompting docs: criteria over fixed numbers, explain the *why*, diverse non-skewed examples; few-shot + surface-feature bias, Zhao et al. 2021): + - *Sparse selection* is expressed as **ANY-of inclusion tests + coverage-by-role + a decision-relevance bar + + "omit is the default"**, with an explicit "guide, not a quota" escape hatch — so it scales from tiny to huge + factories without a hardcoded count (the run guessed with no sense of scale). + - *Inferred vs. annotation* is defined **by one axis — did the deterministic phase already record this as a + lineage edge — explicitly NOT by mechanism**, with an ordered decision rule, an "if in doubt → inferred" + tie-breaker, and diverse sub-case examples (data-in-code, `dependsOn` ordering, shared control asset) plus a + near-miss. The prior single-flavour examples had biased the tier toward the data-in-code sub-case. +- **Two whole-factory recommendation patterns in Step 5 (isolated, revertible).** A second-opinion review of a + real enriched run found the agent tends to *transliterate* (re-implement an ADF tier as a called job) when the + better migration is to *replace* it with a native capability, and does not actively surface *clone families*. + Step 5 now teaches two generic patterns — **"replace, don't transliterate"** (a tier whose sole purpose is a + capability Databricks offers natively → eliminate it: observability→system tables, control tables→task values, + config engines→Python-on-Jobs-API; guarded so it never recommends deleting pipelines that do real work) and + **"collapse clone families"** (cluster by activity signature, emit one insight per family recommending a single + parameterized job with the count). Both reuse the existing insight fields (no schema change) and are kept as a + single self-contained commit so they can be reverted wholesale if the added opinionation proves low-value. +- **No `schema_version` field.** A draft one was removed on #9's branch; it had no consumer. The presence of the + top-level **`insights` key IS the "enriched" marker**. +- **Skip gate: none — always enrich.** The value of `insights` (recovering intent + cross-pipeline + relationships that deterministic analysis structurally cannot recover) holds at every factory size; if + anything it grows with scale, because a per-pipeline converter is most blind to the whole-system picture on a + large factory. Step 5 therefore always authors + enriches. See §9 for the future-review note on partitioning + at scale. +- **Validator is hand-rolled, violation-collecting.** `validate_insights(raw, inventory) -> list[str]` walks the + dict, checks required/unknown fields explicitly, and resolves FKs against sets built from the inventory. No new + dependency; collects **all** violations (not fail-fast) so the agent fixes everything in one pass; matches the + plain-dict style of `merge_agentic_results`. +- **Inline payload reaches the CLI via a temp file.** `_cmd_enrich` materializes an inline `insights` dict to a + temp JSON file and passes `--insights-path` (mirrors `materialize_adf_definitions`), cleaning up after. The CLI + therefore needs only one input mode; `--insights` (raw JSON string) is a thin convenience for direct CLI users. +- **Byte-identical write.** Read `inventory.json` text → `json.loads` → set the single `insights` key → + `json.dumps(obj, indent=2)` write-back, using discover's exact dump options. Existing keys keep order and + formatting; re-running is idempotent. Validation runs **before** any write. + +--- + +## 4. Data models (`models/adf_ast.py`) + +Four new `@dataclass(slots=True, kw_only=True)` types, placed right after `Lineage`. FKs are required (no +default); everything else is optional so authoring stays sparse. + +```python +@dataclass(slots=True, kw_only=True) +class LineageEdgeRef: + """A typed reference from a PipelineRelationship to one cross-pipeline edge. + control/data annotate a deterministic #9 edge; inferred is an agent-asserted coupling.""" + edge_type: Literal["control", "data", "inferred"] + edge_identity: str # control → ControlEdge.activity_name; data → DataEdge.match_key; + # inferred → agent-authored descriptor of the coupling + evidence: str | None = None # required for inferred; must be absent otherwise + confidence: Literal["high", "medium", "low"] | None = None # required for inferred; absent otherwise + +@dataclass(slots=True, kw_only=True) +class PipelineInsight: + """Per-pipeline judgment; references a pipeline by name (FK).""" + pipeline: str # FK → pipelines[].name (validated) + pattern_name: str | None = None + intent: str | None = None + databricks_pattern: str | None = None + recommended_databricks_features: list[str] = field(default_factory=list) + conversion_notes: list[str] = field(default_factory=list) + +@dataclass(slots=True, kw_only=True) +class PipelineRelationship: + """Cross-pipeline judgment; annotates one #9 lineage edge.""" + from_pipeline: str # FK (validated) + to_pipeline: str # FK (validated) + lineage_edge: LineageEdgeRef # must resolve to a real #9 edge (validated) + relationship_summary: str | None = None + databricks_pattern: str | None = None + risk_if_ignored: str | None = None + +@dataclass(slots=True, kw_only=True) +class Insights: + overview: str | None = None + pipeline_insights: list[PipelineInsight] = field(default_factory=list) + pipeline_relationships: list[PipelineRelationship] = field(default_factory=list) +``` + +These dataclasses are the **typed round-trip / serialization** side. Validation of the raw agent JSON is done by +the pure validator (§5) *before* any dataclass is constructed, so unknown/missing fields produce collected +violation strings rather than raw `TypeError`s. + +### Why `LineageEdgeRef` (and not just `from`/`to` + prose) + +`LineageEdgeRef` carries **no facts of its own** — it is a typed foreign key into #9's lineage. It exists for +three reasons: + +1. **Binds judgment to a validatable fact.** A relationship's prose is unanchored on its own; the ref forces it + to point at one real edge (echoing `activity_name` / `match_key`), so `enrich` can resolve it and reject a + relationship that references an edge #9 never found. This is the mechanism that enforces "annotate, don't + rediscover." +2. **Disambiguates multiple facets of one pair.** The same `(from, to)` can be connected by both a control + invocation *and* a data hand-off, each needing a different Databricks pattern and carrying a different risk. + Relationships key on `(from, to, edge_type, edge_identity)`; the ref makes the pair-plus-facet addressable. +3. **Stable across re-runs.** The ref points at #9's canonical keys, not prose or array indices, so a + regenerated lineage still resolves (or fails cleanly if the edge genuinely disappeared). + +--- + +## 5. Parser module (`parser/pipeline_insights.py`) + +New file, sibling to #9's `parser/lineage.py`, shaped like `merge_agentic_results`. + +```python +def load_insights(*, insights: dict | None = None, insights_path: Path | None = None) -> dict: + """Return the RAW insights dict from an inline dict OR a JSON file. + Exactly one source must be provided (not both, not neither). Not yet validated.""" + +def validate_insights(raw: dict, inventory: dict) -> list[str]: + """Pure validator. Returns a list of human-readable violation strings (empty == valid). + Collects ALL violations, never fail-fast. Checks: + - top-level shape: only {overview, pipeline_insights, pipeline_relationships} + - each PipelineInsight: 'pipeline' present & ∈ inventory pipeline names; + no unknown fields; field types (lists are lists, strings are strings) + - each PipelineRelationship: from_pipeline / to_pipeline present & ∈ names; + lineage_edge present, well-formed (edge_type ∈ {control, data, inferred}, edge_identity str), + no unknown fields, and per tier: + control → edge_identity RESOLVES to some ControlEdge.activity_name; no evidence/confidence + data → edge_identity RESOLVES to some DataEdge.match_key; no evidence/confidence + inferred → NOT resolved against lineage; requires non-empty evidence str + and confidence ∈ {high, medium, low} + """ + +def merge_into_inventory(inventory: dict, raw: dict) -> dict: + """Pure. Return a NEW dict identical to `inventory` with exactly one added key, + 'insights', set to `raw`. Does not mutate the input. No I/O.""" + +def enrich_inventory(output_dir: Path, *, insights=None, insights_path=None) -> dict: + """Orchestrator (the only function with I/O): + 1. read /metadata/inventory.json (error if missing) + 2. raw = load_insights(...) + 3. violations = validate_insights(raw, inventory) + 4. if violations: return {ok: False, violations, ...} # NO WRITE + 5. merged = merge_into_inventory(inventory, raw) + 6. write back with json.dumps(merged, indent=2) # byte-identical prior keys + 7. return {ok: True, violations: [], pipeline_insights: N, relationships: M} + """ +``` + +Invariant-locking details: + +- **Validation before I/O** — step 4 returns before any write, satisfying "on failure, do not write the file." +- **FK sets built once** from `inventory["pipelines"][*]["name"]`, `lineage.control_edges[*].activity_name`, and + `lineage.data_edges[*].match_key` — plain set membership, no guessing. +- **Byte-identical** falls out of re-dumping the parsed dict with `indent=2` (discover's exact options) and only + *adding* a key — existing keys keep order and formatting. Idempotent because re-running overwrites `insights` + with an equal value. +- **No `default=str`** (unlike `merge_agentic_results`) — insights are plain JSON scalars/lists; matching + discover's `json.dumps(..., indent=2)` call exactly is what guarantees the byte-for-byte round-trip. + +--- + +## 6. Adapter subcommand & MCP command + +### 6a. Adapter CLI subcommand `enrich` (`adapter/__main__.py`, modeled on `record-results`) + +Standalone subcommand — deliberately **not** a `discover` flag — so it re-runs without re-parsing ADF. + +```python +enrich = subparsers.add_parser( + "enrich", + help="Merge agent-authored insights into metadata/inventory.json (validate + append).", +) +enrich.add_argument("--output-dir", type=Path, required=True, + help="Migration output directory (reads/writes metadata/inventory.json).") +enrich.add_argument("--insights-path", type=Path, default=None, + help="Path to a JSON file holding the insights object.") +enrich.add_argument("--insights", type=str, default=None, + help="Insights object as an inline JSON string (convenience for direct CLI use).") + +# in main(): +if args.command == "enrich": + return _run_enrich(args) +``` + +```python +def _run_enrich(args) -> int: + """Validate + merge insights into inventory.json. Returns 0 on success, 1 on any failure + (missing inventory, unreadable/absent/both payload sources, validation violations).""" + from flowx.parser.pipeline_insights import enrich_inventory + metadata_dir = args.output_dir / "metadata" + if not (metadata_dir / "inventory.json").exists(): + print(f"No inventory.json under {metadata_dir}; run discover first.", file=sys.stderr) + return 1 + # resolve exactly one payload source (parse --insights JSON string if given) + result = enrich_inventory(args.output_dir, insights=, insights_path=args.insights_path) + if not result["ok"]: + for v in result["violations"]: + print(f" - {v}", file=sys.stderr) + print(f"Insights validation failed ({len(result['violations'])} violation(s)); " + f"inventory not modified.", file=sys.stderr) + return 1 + print(f"Enriched inventory: {result['pipeline_insights']} pipeline insight(s), " + f"{result['relationships']} relationship(s).") + return 0 +``` + +### 6b. MCP command `_cmd_enrich` (`mcp/server.py`, added to `_COMMANDS` as `"enrich"`) + +Mirrors `_cmd_discover`'s inline-payload handling: materialize the inline dict to a temp file, pass +`--insights-path`, clean up. + +```python +def _cmd_enrich(p: dict[str, Any]) -> dict[str, Any]: + output_dir = p.get("output_dir", "./flowx_output") + insights = p.get("insights") # inline dict (hosted / Genie path) + insights_path = p.get("insights_path") # readable path (local path) + if insights is None and not insights_path: + return {"ok": False, "error": "Provide 'insights' (inline dict) or 'insights_path'."} + tmp = None + try: + if insights is not None: + tmp = runner.materialize_json(insights) # temp file, mirrors materialize_adf_definitions + insights_path = tmp + args = ["enrich", "--output-dir", output_dir, "--insights-path", insights_path] + result = runner.run_adapter(args) + out = Path(output_dir) + return _phase_result(result, out, inventory=runner.summarize_inventory(out)) + finally: + if tmp: + runner.cleanup_materialized(tmp) +``` + +Additions: +- `runner.materialize_json(obj) -> str` — writes `json.dumps(obj)` to a temp file, parallel to + `materialize_adf_definitions`. +- Register `"enrich": _cmd_enrich` in `_COMMANDS`; update the `flowx` tool docstring / `_COMMANDS` param docs to + list `enrich`. +- Confirm the runner captures subprocess stderr into `result` so validation violation lines surface to the + agent; if not, `_cmd_enrich` parses and echoes them explicitly. + +--- + +## 7. Skill Step 5 (`skills/flowx-discover/SKILL.md`) + +Insert the **author → enrich** loop as the new **Step 5**; renumber the existing reporting steps (5–8) down. + +**New Step 5 — Author and merge agentic insights** (always runs): + +1. **Read** the just-written `inventory.json` (`pipelines`, `lineage`, `summary`) plus `profile_report.csv`. +2. **Author** an `insights` object: + - `overview` — the whole factory as one system + the single biggest migration steer. + - `pipeline_insights[]` — **sparse**; per-pipeline `intent` + `databricks_pattern` (+ optional + features/notes). Omit pipelines with nothing worth saying. + - `pipeline_relationships[]` — annotate #9 lineage edges: each carries a `lineage_edge` + (`edge_type` + `edge_identity` echoed verbatim from a real edge — `activity_name` for control, `match_key` + for data), plus `relationship_summary`, `databricks_pattern`, `risk_if_ignored`. + - Authoring rules in the prose: reference only pipeline names that exist; every `lineage_edge` must echo a + real edge; don't invent relationships #9 didn't find (annotate, don't rediscover). +3. **Enrich** — call `flowx(command="enrich", parameters={"output_dir": ..., "insights": {...}})` (inline dict on + the hosted/Genie path; `insights_path` on the local CLI path). Local CLI fallback: + `"$PY" -m flowx.adapter enrich --output-dir --insights-path `. +4. **On `ok:false`** — the tool did **not** write; read `violations`, fix the offending FK/edge/field, and + re-call. On `ok:true`, the `insights` key is merged into `inventory.json`. + +**Reworded summary step** (the old "Present the summary"): after the counts table, surface the enriched +judgment — the factory `overview`, and per-pipeline `pattern_name` / `intent` — so the user sees *what the +factory does* and the recommended Databricks patterns, not just coverage numbers. + +--- + +## 8. Testing & optional reporting + +**Test file:** `tests/unit/test_pipeline_insights.py`, following `test_merge_agentic.py` precedent (helper +writers, `tmp_path`, assert **structure/schema — never prose**). **No live LLM** — all fixtures are stubbed JSON. + +**Fixtures** under `tests/resources/json/`: +- A small **good** `insights` object referencing a known inventory. +- Edge-binding tests reuse the existing `pipeline_execute_pipeline_nested.json`, whose known + `ControlEdge.activity_name` values are `"Run Ingestion Pipeline"`, `"Run Transform Pipeline"`, and + `"Run Cleanup Pipeline"`. The test builds a real inventory dict from it (via `load_adf_definitions` + + `build_lineage` + `_inventory_to_dict`, mirroring `test_lineage.py`) so FK/edge sets are genuine, not + hand-faked. + +**Test cases (1:1 with the issue's invariants):** + +| Test | Asserts | +|---|---| +| `test_validator_accepts_good_insights` | `validate_insights` returns `[]`; `enrich_inventory` returns `ok:True` with correct counts | +| `test_rejects_pipeline_not_in_inventory` | FK `pipeline`/`from_pipeline`/`to_pipeline` not in names → non-empty violations | +| `test_rejects_unresolvable_lineage_edge` | `lineage_edge` with no matching edge → violation | +| `test_rejects_missing_required_field` | missing `pipeline` / `from_pipeline` / `lineage_edge` → violation | +| `test_rejects_unknown_field` | extra key in any insight/relationship → violation | +| `test_control_edge_binding_matches_and_rejects` | on the nested fixture: `edge_identity="Run Ingestion Pipeline"` validates; a bogus name rejects | +| `test_data_edge_binds_on_match_key` | data edge resolves on `match_key`; a non-matching key rejects | +| `test_inferred_edge_with_evidence_and_confidence_validates` | `inferred` edge with real endpoints + non-empty `evidence` + valid `confidence` → `[]` | +| `test_inferred_edge_requires_evidence` | `inferred` edge missing `evidence` → violation | +| `test_inferred_edge_requires_valid_confidence` | `inferred` edge with bad/missing `confidence` → violation | +| `test_inferred_edge_does_not_resolve_against_lineage` | `inferred` `edge_identity` is *not* checked against lineage sets (arbitrary descriptor validates) | +| `test_annotation_edge_rejects_evidence_confidence` | `control`/`data` edge carrying `evidence`/`confidence` → violation (inferred-only fields) | +| `test_two_pass_deterministic_keys_byte_identical` | pre-enrich vs post-enrich: all keys except `insights` byte-identical | +| `test_enrich_is_idempotent` | running `enrich_inventory` twice → identical file bytes | +| `test_validation_failure_does_not_write` | on violations: `ok:False`, `violations` populated, **file unchanged on disk** | + +**Optional reporting** (`reporting/coverage.py`): a `has_insights` (bool) or `pattern_name` column per pipeline, +**gated on the `insights` key existing** so non-enriched inventories are unaffected. Added only if it stays +trivial and doesn't perturb existing coverage tests; the issue marks it optional, so it will not hold up the +core. + +**End-to-end verification** (before declaring done): a real `discover` then `enrich` over +`tests/resources/json/`, confirming the inventory gains a valid `insights` block while the deterministic keys are +byte-identical. Plus full unit suite + `make fmt` (ruff + mypy) clean. + +--- + +## 9. Scope + +**In scope:** +- The `insights` schema + 4 dataclasses in `models/adf_ast.py`. +- `parser/pipeline_insights.py` (`load_insights` → `validate_insights` → `merge_into_inventory` → + `enrich_inventory`). +- The `enrich` adapter subcommand + `_cmd_enrich` MCP command (+ `runner.materialize_json`). +- The two-pass byte-identical write. +- Step 5 in `flowx-discover` (always runs) + reworded summary step. +- Optional reporting column. + +**Out of scope:** +- Consuming insights in `convert` (separate follow-up). +- The #9 extraction itself; a new skill/phase; an in-code LLM client. +- Per-activity insights; domains grouping; per-edge narrative; #10 deploy-ordering. +- Any `schema_version` field. + +**Future considerations (review later — not #11):** +- Insights are currently authored in a single pass over the whole factory. For very large factories, revisit + **partitioning the authoring across subagents keyed on lineage clusters** — the connected components of the + combined control/data-edge graph — so each subagent reasons about one coherent subsystem rather than the whole + corpus at once. The open question that motivates this is "are all these pipelines even related?"; the lineage + graph already holds the answer. For #11 we always enrich in one pass. + +--- + +## 10. Customer confidentiality + +All **code, tests, fixtures, comments, commit messages, and the PR body** use generic placeholders only — they +must **never** contain real customer names or customer-derived vocabulary. + +- **Placeholders to use everywhere:** "Factory A"/"Factory B" (factories), `entityID` (loop key), + `config-params/entity-versions` (watermark path), "dummy dataset" (any dataset name). +- **Denylist — this design doc only, as a validation reference:** the terms below are recorded here **solely so + we can grep the diff, commits, and PR against them to confirm none leaked**. They must not appear in any + shipped artifact (code/tests/fixtures/comments/commits/PR): "a customer factory", "a large factory", `engagementID`, + `engagementDBVersions`, `etl-parameters`. A pre-flight check before opening the PR greps the branch for each of + these and must return zero hits outside this spec file. diff --git a/skills/flowx-discover/SKILL.md b/skills/flowx-discover/SKILL.md index b27e6f0..1d4a201 100644 --- a/skills/flowx-discover/SKILL.md +++ b/skills/flowx-discover/SKILL.md @@ -58,7 +58,461 @@ Both paths are the same across sources; only `--source` and the source path diff `--source-path` is the generic flag (each source also accepts its own alias, e.g. `--adf-source-path`); both normalise to the phase's `--source-dir`. `--source` is required. -## Output artifacts (shared across sources) +## Workflow + +Follow these steps in order: + +### Step 1 — Determine the ADF source path + +Ask the user for the location of their ADF JSON exports. Accept either: +- A Unity Catalog volume path (e.g., `/Volumes/main/default/adf_export`) +- A local directory path (e.g., `./adf_export/` or `/tmp/adf_json/`) + +The directory should contain subdirectories or files for: +- `pipeline/` or `pipelines/` — pipeline definition JSON files +- `dataset/` or `datasets/` — dataset definition JSON files (optional) +- `linkedService/` or `linked_services/` — linked service JSON files (optional) +- `trigger/` or `triggers/` — trigger definition JSON files (optional) + +### Step 2 — Download from UC volumes if needed + +If the source path starts with `/Volumes/`, the files live in a Unity Catalog volume and must be downloaded to a local temp directory first. + +Use the `databricks-execution-compute` skill to run the following on the Databricks workspace: + +```python +import os, json, shutil, tempfile + +volume_path = "" +local_dir = tempfile.mkdtemp(prefix="adf_ingest_") + +# Copy from volume to local +for root, dirs, files in os.walk(volume_path): + for f in files: + if f.endswith(".json"): + src = os.path.join(root, f) + rel = os.path.relpath(src, volume_path) + dst = os.path.join(local_dir, rel) + os.makedirs(os.path.dirname(dst), exist_ok=True) + shutil.copy2(src, dst) + +print(f"Downloaded ADF files to: {local_dir}") +``` + +Alternatively, use the Databricks CLI: +```bash +databricks fs cp -r "dbfs:" "" --overwrite +``` + +Set the working source directory to the local temp path for subsequent steps. + +### Step 3 — Run the deterministic parser + +Run the discover phase via the adapter's unified phase runner (recommended): + +```bash +"$PY" -m flowx.adapter discover \ + --adf-source-path \ + --output-dir \ + [--pipeline ] +``` + +`--adf-source-path` is accepted as an alias of `--source-dir` (it matches the +`adf_source_path` input option). This forwards to, and is equivalent to, running +the loader directly: + +```bash +"$PY" -m flowx.parser.adf_loader \ + --source-dir --output-dir [--pipeline ] +``` + +Where: +- `` is the root of the flowx plugin (the directory containing `src/`) +- `` is the local directory containing ADF JSON files +- `` is the **single shared migration output directory** used by all three phases + (default: `./flowx_output`). Discover writes its artifacts into the `metadata/` subfolder. +- `` (optional) — when provided, filters to only the named pipeline. When omitted, all pipelines in the source directory are included. + +**Always pass `--pipeline` when the user has specified a specific pipeline to migrate.** This ensures the inventory and all downstream phases are scoped to only that pipeline. + +This produces, under `/metadata/`: +- `inventory.json` — the classified activity inventory +- `profile_report.csv` — one row per pipeline with a complexity assessment (see Step 4b) +- `.arm.json` — the verbatim original ADF/ARM source for each pipeline (provenance) + +### Step 4 — Read and validate the inventory + +Read the generated `/metadata/inventory.json` file. It has this structure: + +```json +{ + "source_dir": "/path/to/adf/json", + "generated_at": "2026-04-07T12:00:00Z", + "pipelines": [ + { + "name": "PipelineName", + "file": "pipeline/PipelineName.json", + "activities": [ + { + "name": "CopyFromBlob", + "type": "Copy", + "strategy": "deterministic", + "translator": "copy.py" + }, + { + "name": "RunDataFlow", + "type": "ExecuteDataFlow", + "strategy": "agentic" + } + ] + } + ], + "summary": { + "pipeline_count": 12, + "activity_count": 47, + "deterministic_count": 35, + "agentic_count": 10, + "unsupported_count": 2, + "coverage_pct": 95.7 + }, + "lineage": { + "control_edges": [ + { + "caller_pipeline": "ETL_Main", + "callee_pipeline": "Load_Dim_Customer", + "activity_name": "Run Customer Load", + "wait_on_completion": true + } + ], + "data_edges": [ + { + "dataset_name": "curated_customer", + "identity": "abfss://curated/customer", + "producer_pipeline": "Load_Dim_Customer", + "producer_activity": "WriteCustomer", + "consumer_pipeline": "Build_Sales_Mart", + "consumer_activity": "ReadCustomer", + "match_kind": "identity", + "match_key": "abfss://curated/customer" + } + ] + } +} +``` + +The `lineage` block records the cross-pipeline edges the discover phase +recovered — `control_edges` (one pipeline invokes another via ExecutePipeline; +identified by `activity_name`) and `data_edges` (one pipeline writes a dataset +another reads; identified by `match_key`). Step 5 annotates these edges, so it +depends on this block being present. When a factory has no edges of a kind, its +list is empty (`[]`). + +### Step 4b — Review the complexity report + +`/metadata/profile_report.csv` carries one row per pipeline with a migration-complexity +assessment. Columns: + +| Column | Meaning | +|---|---| +| `pipeline` | Pipeline name | +| `activities` | Total activities (including nested ForEach/If/Switch children) | +| `datasets` | Distinct datasets the pipeline references | +| `linked_services` | Distinct linked services (activity-level + via referenced datasets) | +| `collapsible_patterns` | Number of motif patterns detected (auto-collapsible during convert) | +| `databricks_native_activities` | Notebook / SparkJar / SparkPython / Job activities (simplest) | +| `control_flow_activities` | ForEach / If / Switch / SetVariable / AppendVariable / Filter / Wait / Until | +| `other_activities` | Everything else — Copy, Web, Lookup, agentic types (hardest) | +| `complexity_score` | Weighted score: native×1 + control×2 + other×3 + datasets + linked_services + collapsible_patterns | +| `complexity_size` | T-shirt size from the score: **S** ≤5, **M** ≤15, **L** ≤30, **XL** >30 | + +Use it to set expectations: S/M pipelines are largely deterministic; L/XL pipelines (many "other" +activities, datasets, or linked services) warrant closer review and more agentic translation. + +### Step 5 — Author and merge agentic insights + +The deterministic inventory records *what* each pipeline contains; it cannot +record *what the factory is trying to do* or *how the pipelines relate as a +system*. Author that judgment now and merge it into `inventory.json` under an +`insights` key. This always runs. + +1. **Read** the just-written `inventory.json` (`pipelines`, `lineage`, `summary`) + and `profile_report.csv`. **Then, before authoring, deep-dive the source.** + The inventory is a deterministic skeleton (types, strategy, control edges); the + *why* and *how* — queries, Switch conditions, notebook paths, dataset + parameters — live only in the verbatim ARM. The `metadata/` folder holds one + `*.arm.json` file per pipeline; each file is a **flat single-pipeline object** + shaped `{"name": "", "properties": {"activities": [...], ...}}` (no + `resources[]` array, no top-level `type`). To inspect a pipeline, **glob + `metadata/*.arm.json` and match on each file's top-level `"name"` field** — do + **not** construct a filename from the pipeline name (names are slugified and + lossy, so a built path can miss or collide). The activities are under + `properties.activities` (recurse into nested `ForEach`/`If`/`Switch` bodies). + Read the ARM for any pipeline you write an insight or relationship about. +2. **Author** an `insights` object: + - `overview` — the whole factory as one system, plus the single biggest + migration steer. + - `system_recommendation` *(optional; preferred on any multi-pipeline factory)* — + the **one top-level architectural decision** a migrator must make **before** any + per-pipeline work, because it *cascades* across pipelines. A per-pipeline card + alone can't show it: e.g. "adopt a managed connector for the whole extraction + family" turns the child extractors into connector pipelines, deletes the + watermark store, **and** empties the fan-out orchestrator all at once. Author + this **first**, then keep each `pipeline_insights[].recommended_patterns` + consistent with the branch it recommends. Fields: + - `headline` — one line naming the decision (e.g. "Managed ingestion collapses + the extraction factory"). + - `recommended_patterns` — **1–4 whole-system branches**, ranked best-first and + shaped exactly like a pipeline's (each with `pattern`, `fit`, + `simplification_pattern`). `[0]` is the recommended branch; + later entries are the ranked fallbacks. Example: `[0]` = "Adopt **Lakeflow + Connect** for the whole SQL Server extraction family" (`simplification_pattern: + true`); `[1]` = "For-each orchestrator + 2 collapsed parameterized jobs" + (`simplification_pattern: false`). + - `cascade` — what choosing `[0]` **collapses or eliminates across the system** + (e.g. "5 child extractors → managed connector pipelines"; "version-watermark + CSV → gone"; "fan-out orchestrator → near-empty"). This is the payoff the + reader cannot see from any one pipeline. Omit (or `[]`) when the decision does + not cascade. + - `decision_driver` *(optional)* — the gating question that picks the branch + (e.g. "Is the Lakeflow Connect SQL Server connector GA/approved for this + source?"). + Use it whenever a **system-wide** capability (a managed connector for a whole + source, one observability tier, one control layer) would reshape many pipelines + at once; skip it for a single isolated pipeline. + - `pipeline_insights[]` — a **sparse, selective** list (per entry an `intent` + and `recommended_patterns`; optionally `pattern_name`, `databricks_pattern`, + `conversion_notes`, `risk_if_ignored`). + Omitting a pipeline is the default and needs no justification — a short, + high-signal list the reader can trust beats a note on every pipeline. + - **`risk_if_ignored`** (optional) — a one-line consequence a migrator faces + if they port this pipeline naively (e.g. "Switch-nested calls are invisible + in `lineage.control_edges`, so this reads as a leaf"). Use it only when the + insight carries a genuine migration hazard; otherwise omit. + - **`recommended_patterns` — the grounded, ranked recommendation.** A list of + **1–4** Databricks target patterns for this pipeline, ordered **best-first**. + Author it from a **holistic read of the whole pipeline** — its activities, + dependencies, datasets, linked-service source types, parameters, and intent — + **not** from a single pattern label. Each entry is an object: + - `pattern` — the **named, publicly-documented** Databricks capability (e.g. + `Lakeflow Connect SQL Server connector`, `Auto Loader`, + `Lakeflow Declarative Pipelines AUTO CDC`). Name **only** capabilities that + actually exist; **docs.databricks.com is the reference**. Never invent a name. + - `fit` — one line: why it fits *this* pipeline / what bespoke logic it replaces. + - `simplification_pattern` — `true` **only** when the pattern uses a *distinctive* + Databricks capability that collapses or eliminates a whole legacy pattern: + a managed connector (**Lakeflow Connect**), declarative CDC (**`AUTO CDC`**), + **Auto Loader**, or **system tables** replacing a home-grown logging tier. + Set it `false` for a like-for-like port **and** for plain native building + blocks that merely re-home the same work — a bare parameterized **Lakeflow + Job**, a for-each/run-job orchestrator, a plain Delta control table, + `MERGE INTO`. "Runs on Databricks" is **not** a simplification: almost + everything you migrate is native, so reserve this flag for the capability + that makes the old pattern *disappear*. Rank the `true` patterns **first**. + + **Rank simplification-first:** prefer managed ingestion over a hand-rolled + extract, declarative CDC over custom watermark logic, and collapsing clones + over N ports — but flag `simplification_pattern: true` only on the entries that + truly use a distinctive capability, not on the plain-orchestration fallback. + Keep it to 1–4 (don't pad); **omit the field** when you have no grounded + recommendation. Note GA/Preview status in `fit` when it affects the decision — + **verify** a connector's status in the docs/release notes (e.g. the Lakeflow + Connect SQL Server connector) rather than assuming GA. + + **Recognized-pattern vocabulary — a reference menu, NOT an allowlist.** Common + ADF→Databricks target patterns with **current** product names. Use it to stay + grounded and consistent, but reach past it whenever the holistic view calls for a + better or newer fit: + + | Pipeline does… | Simplifying target — `simplification_pattern: true` (rank first) | Fallback — `false` | + |---|---|---| + | Extract/Copy from a database (SQL Server, …) | **Lakeflow Connect** managed connector (change-tracking/CDC → Delta) | Auto Loader / JDBC read + `MERGE INTO` | + | Incremental load via watermark | **Lakeflow Declarative Pipelines `AUTO CDC`** | Delta `MERGE INTO` + control table / `dbutils.jobs.taskValues` | + | CDC / SQL Server change tracking | **Lakeflow Connect** or **`AUTO CDC`** | Structured Streaming over the change feed | + | Land + process files | **Auto Loader** (`cloudFiles`, file-notification mode) | — | + | Metadata-driven bulk copy (Lookup→ForEach→Copy) | **Lakeflow Connect** (multi-table) or a parameterized **Lakeflow Jobs** for-each task | — | + | Parent/child `ExecutePipeline` fan-out | **Lakeflow Jobs** for-each task + run-job task + job parameters | — | + | SCD Type 2 (data flow) | **Lakeflow Declarative Pipelines `AUTO CDC`** (SCD Type 2) | — | + | Staged load + stored-proc transform | Spark write to **Delta** + post-load step | — | + | REST API pagination | Python ingestion notebook (requests-based) | Lakeflow Connect SaaS connector if one fits | + | Custom logging / observability tier | **system tables (`system.lakeflow.*`) + native job notifications + AI/BI dashboard** | — | + | Run-state / control tables | Lakeflow job & task run state + `dbutils.jobs.taskValues` | — | + | Clone family (many near-identical pipelines) | one **parameterized Lakeflow Job** invoked N times | — | + + **Emit current names, not legacy ones:** Lakeflow Jobs (was Databricks + Workflows), Lakeflow Declarative Pipelines (was Delta Live Tables/DLT), `AUTO CDC` + (was `APPLY CHANGES INTO`), Declarative Automation Bundles (was Databricks Asset + Bundles), AI/BI dashboards (was Lakeview), `system.lakeflow` (was + `system.workflow`). + - **`databricks_pattern`** (optional) — a one-line **headline** naming the primary + target architecture. `recommended_patterns[0]` is the structured form of it, so + omit `databricks_pattern` unless a short prose headline genuinely adds signal. + - **Include a pipeline only if it meets ANY of these tests:** it anchors a + reusable *framework* or *pattern* many others depend on (an orchestrator, + a shared engine/wrapper, a logging/control-table hub); its classification + or role is *surprising* given its name; or it carries a *risk or caveat* a + migrator must know before porting it. + - **Cover distinct roles, not a fixed count.** One representative note per + notable role/archetype is usually enough — if forty pipelines are near- + identical wrappers around one engine, note the engine and one representative + wrapper, not all forty. Scale is set by how many *distinct* roles exist, not + by pipeline count: on a large factory you will typically flag only a small + minority. This is a guide, not a quota — include fewer if fewer qualify. + - Rule of thumb: include a note only if it would **change a reader's decision + or surprise a domain expert**. When in doubt, omit. + - **Two whole-factory recommendation patterns** (apply when the evidence is + there; use `pattern_name` to tag them, record the target as a + `recommended_patterns` entry with `simplification_pattern: true`, and + quantify the payoff in `intent` / `conversion_notes`. When either reshapes + the *whole* system, also surface it as the `system_recommendation`): + - **Replace, don't transliterate.** When a *whole tier or sub-factory* + exists only to provide a capability Databricks offers **natively**, + recommend eliminating it, not re-implementing it as a called job. Common + generic mappings: a logging/observability tier → system tables + (`system.lakeflow.*`) + native Lakeflow job notifications + an AI/BI + dashboard; run-state / control tables → Lakeflow job & task run state and + `dbutils.jobs.taskValues`; a config-driven Switch/template "engine" with + no native equivalent → a Python orchestrator driving the Jobs API. **Guard + against over-firing:** only recommend REPLACE when the tier's *sole* + purpose is the native capability (e.g. it only logs / only records run + state). If a pipeline does real domain work alongside the boilerplate, + migrate it normally — do not tell the reader to delete real logic. + - **Collapse clone families.** Cluster pipelines by their activity + *signature* (ordered activity types) and shared child-edge set across the + whole inventory. Where a family of near-identical pipelines exists, emit + **one** insight (anchored on a representative pipeline that exists in the + inventory) that names the family and its count, recommends collapsing the + N clones into a **single parameterized job invoked N times**, and + quantifies the win (e.g. "14 `LAAE_ingest_*` pipelines, identical + `[IfCondition, IfCondition, ExecutePipeline×4]` signature → 1 parameterized + job"). List the members in `conversion_notes`. This supersedes writing N + near-duplicate per-pipeline notes. + - `pipeline_relationships[]` — characterize **how data and control flow + between the pipelines**, whatever the mechanism. Each relationship carries + `from_pipeline` and `to_pipeline` (both must be pipeline names that exist in + the inventory) plus a `lineage_edge`, `relationship_summary`, + `databricks_pattern`, and `risk_if_ignored`. + + **A `lineage_edge` is one of two tiers. The tier is decided by ONE thing: + whether the deterministic phase already recorded this coupling as an edge in + `lineage` — NOT by the coupling's mechanism** (control call, dataset, table + written in notebook code, ordering dependency, external trigger, …). Do not + classify by mechanism. + + - **Annotation** (`edge_type` = `control` or `data`) — the coupling is + *already* an edge in `lineage`; you are adding interpretation to it. + `edge_identity` is copied **verbatim** from that edge: the `activity_name` + for a `control_edges` entry, the `match_key` for a `data_edges` entry. Do + not add `evidence` / `confidence`. + - **Inferred** (`edge_type` = `inferred`) — a *real* coupling the + deterministic phase did **not** record as an edge, by any mechanism. Set + `edge_identity` to a short descriptor of what couples the two pipelines + (e.g. the shared table/asset, or the nature of the dependency), and **you + must** supply `evidence` (the concrete ARM observation behind it) and + `confidence` (`high` / `medium` / `low`). Report only couplings you can + actually evidence; do not invent them. + + **Decide in order:** + 1. Is this coupling already an edge in `lineage` (a `control_edges` / + `data_edges` entry)? → **annotation** (`control` / `data`). + 2. Otherwise, is it a real coupling not present in `lineage`? → **inferred**. + 3. If in doubt — the coupling is real but you cannot point to the `lineage` + edge that names it — classify it **inferred** (never annotate an edge that + is not there). + + **Inferred covers several sub-cases — do not restrict it to any one:** + - *Data-in-code:* one pipeline's notebook writes a table another's notebook + reads (no ADF dataset, so `data_edges` never saw it). + - *Ordering dependency:* a producer→consumer hand-off expressed only as + sibling `dependsOn` inside a parent orchestrator, which the deterministic + phase did not emit as a cross-pipeline edge. + - *Shared control/config asset, external trigger, message queue,* or any + other real coupling flowx could not represent. + - *Near-miss (this is an annotation, not inferred):* pipeline A calls B via + ExecutePipeline and that call is already a `control_edges` entry — even + though B then does its real work in a notebook, the coupling itself was + recorded, so annotate it. + - **Authoring rules:** reference only pipeline names that exist in the + inventory; an annotation edge (`control`/`data`) must echo a real lineage + edge (annotate, don't rediscover); an `inferred` edge must carry non-empty + `evidence` and a `confidence` level and must not be dressed up as proven + lineage. +3. **Enrich** — merge the object in: + + - **MCP tool path** (inline dict; the only path in Genie Code): + + ``` + flowx(command="enrich", parameters={ + "output_dir": "", + "insights": { ...authored object... }}) + ``` + + - **venv CLI fallback** (write the object to a JSON file first): + + ```bash + "$PY" -m flowx.adapter enrich --output-dir --insights-path + ``` + +4. **On `ok:false`** the tool did **not** write the file: read `violations`, fix + the offending pipeline name / lineage edge / field, and call `enrich` again. + On `ok:true` the `insights` key is now merged into `inventory.json`. + +### Step 6 — Present the summary + +Display a summary table to the user: + +``` +ADF Discovery Summary +===================== +Pipelines parsed: 12 +Total activities: 47 + +Strategy Breakdown: + Deterministic: 35 (74.5%) + Agentic: 10 (21.3%) + Unsupported: 2 ( 4.3%) + +Coverage: 95.7% +``` + +Then surface the authored judgment so the user sees *what the factory does*, not +just coverage numbers: print the factory `overview`, and for each +`pipeline_insights` entry its `pattern_name` / `intent` and its top +`recommended_patterns` (ranked simplification-first). Read these back from the +enriched `inventory.json`. + +### Step 7 — Detail agentic activities + +For activities classified as `agentic`, explain that each is translated by the agent using LLM-assisted reasoning from the activity's ARM JSON (no built-in deterministic translator exists for these types): + +| Activity | Type | Handling | +|---|---|---| +| RunDataFlow | ExecuteDataFlow | Agentic (LLM-assisted) | +| BranchLogic | Switch | Agentic (LLM-assisted) | +| ... | ... | ... | + +### Step 8 — Warn about unsupported activities + +For activities classified as `unsupported`, warn the user clearly: + +``` +WARNING: The following activities have no automated translation path: + - Pipeline "ETL_Main" / Activity "RunSSIS" (ExecuteSSISPackage) + Recommendation: Manual conversion to PySpark notebook required. +``` + +### Step 9 — Confirm output location + +Tell the user where the metadata files were written (`/metadata/`: inventory.json, profile_report.csv, and the per-pipeline `.arm.json`), summarise the complexity sizes, and confirm they can proceed to the `convert` phase using the same ``. + +## Examples + +- "Discover my ADF pipelines from /Volumes/main/default/adf_export" +- "Parse ADF definitions from ./tests/resources/json/" +- "Load the ADF pipeline JSON files and show me the inventory" +- "Import pipelines from /tmp/customer_adf_export" +- "Discover only the pl_demo_01 pipeline from /Volumes/main/default/adf_export" + +## Output Artifacts All under the shared `/metadata/` folder: @@ -66,15 +520,12 @@ All under the shared `/metadata/` folder: |---|---| | `metadata/inventory.json` | Classified activity inventory for the convert phase | | `metadata/profile_report.csv` | Per-pipeline complexity report (counts + T-shirt size) | -| `metadata/.arm.json` | (ADF) Verbatim original source for each pipeline (provenance) | - -The inventory classifies every task into one of three strategies: - -- **Deterministic** — a built-in translator exists; converted without an LLM. -- **Agentic** — requires LLM-assisted translation from the source definition. -- **Unsupported** — no known translation path; needs manual intervention. +| `metadata/.arm.json` | Verbatim original ADF/ARM source for each pipeline | -## Reference +## Future considerations -- `sources/adf.md` — Azure Data Factory discovery (ARM JSON, UC-volume download, complexity report) -- `sources/airflow.md` — Apache Airflow discovery (DAG `.py` parsing, operator classification) +Insights are currently authored in a single pass over the whole factory. For very +large factories, revisit partitioning the authoring across subagents keyed on +lineage clusters (the connected components of the combined control/data-edge +graph), so each subagent reasons about one coherent subsystem. Out of scope for +now — always enrich in one pass. diff --git a/src/flowx/adapter/__main__.py b/src/flowx/adapter/__main__.py index 38545be..6691990 100644 --- a/src/flowx/adapter/__main__.py +++ b/src/flowx/adapter/__main__.py @@ -87,6 +87,8 @@ def main(argv: list[str] | None = None) -> int: return _run_resolve_agentic(args) if args.command == "record-results": return _run_record_results(args) + if args.command == "enrich": + return _run_enrich(args) if args.command == "install-dashboard": return _run_install_dashboard(args) parser.print_help(sys.stderr) @@ -163,6 +165,51 @@ def _run_record_results(args: argparse.Namespace) -> int: return 0 +def _run_enrich(args: argparse.Namespace) -> int: + """Implements ``enrich``: validate + merge agent-authored insights into inventory.json. + + Returns 0 on success, 1 on any failure (missing inventory, unreadable/absent/ + both payload sources, or validation violations). + """ + from flowx.parser.pipeline_insights import enrich_inventory + + metadata_dir = args.output_dir / "metadata" + if not (metadata_dir / "inventory.json").exists(): + print(f"No inventory.json under {metadata_dir}; run the discover phase first.", file=sys.stderr) + return 1 + + inline: dict[str, Any] | None = None + if args.insights is not None: + try: + inline = json.loads(args.insights) + except json.JSONDecodeError as error: + print(f"Invalid --insights JSON: {error}", file=sys.stderr) + return 1 + if (inline is None) == (args.insights_path is None): + print("Provide exactly one of --insights (inline JSON) or --insights-path.", file=sys.stderr) + return 1 + + try: + result = enrich_inventory(args.output_dir, insights=inline, insights_path=args.insights_path) + except (OSError, ValueError, json.JSONDecodeError) as error: + print(f"Failed to enrich inventory: {error}", file=sys.stderr) + return 1 + + if not result["ok"]: + for violation in result["violations"]: + print(f" - {violation}", file=sys.stderr) + print( + f"Insights validation failed ({len(result['violations'])} violation(s)); inventory not modified.", + file=sys.stderr, + ) + return 1 + print( + f"Enriched inventory: {result['pipeline_insights']} pipeline insight(s), " + f"{result['relationships']} relationship(s)." + ) + return 0 + + def _run_install_dashboard(args: argparse.Namespace) -> int: """Implements ``install-dashboard``: create + publish the coverage dashboard. @@ -498,6 +545,29 @@ def _build_parser() -> argparse.ArgumentParser: help="SQL warehouse id for the write. Auto-detected (prefers running serverless) when omitted.", ) + enrich = subparsers.add_parser( + "enrich", + help="Validate and merge agent-authored insights into metadata/inventory.json.", + ) + enrich.add_argument( + "--output-dir", + type=Path, + required=True, + help="Migration output directory (reads/writes metadata/inventory.json).", + ) + enrich.add_argument( + "--insights-path", + type=Path, + default=None, + help="Path to a JSON file holding the insights object.", + ) + enrich.add_argument( + "--insights", + type=str, + default=None, + help="Insights object as an inline JSON string (convenience for direct CLI use).", + ) + dashboard = subparsers.add_parser( "install-dashboard", help="Create and publish an AI/BI dashboard visualizing coverage from the results table.", diff --git a/src/flowx/mcp/runner.py b/src/flowx/mcp/runner.py index bc6986e..e173186 100644 --- a/src/flowx/mcp/runner.py +++ b/src/flowx/mcp/runner.py @@ -204,15 +204,45 @@ def materialize_adf_definitions(definitions: dict[str, Any]) -> str: return str(base) -def cleanup_materialized(source: str) -> None: - """Remove a temp tree created by :func:`materialize_adf_definitions`. +def materialize_json(obj: Any) -> str: + """Write a JSON-serialisable object to a temp file and return its path. + + Lets the MCP server pass an inline ``insights`` dict to the adapter's + ``enrich`` subcommand (which reads from ``--insights-path``). Clean up with + :func:`cleanup_materialized`. + """ + fd, path = tempfile.mkstemp(prefix="flowx-insights-", suffix=".json") + with os.fdopen(fd, "w", encoding="utf-8") as handle: + json.dump(obj, handle) + return path + - Accepts either the returned directory or the single-file path (whose parent temp dir is - removed). Only paths under the system temp dir are deleted, as a safety guard. +_TEMP_DIR_PREFIXES = ("flowx-adf-", "flowx-vol-", "flowx-ws-") + + +def cleanup_materialized(source: str) -> None: + """Remove a temp tree/file created by :func:`materialize_adf_definitions`, + :func:`download_volume_dir`, :func:`download_workspace_dir`, or :func:`materialize_json`. + + Accepts a temp directory path (from the ``mkdtemp`` helpers), a single-file path + inside such a directory (the single ARM-template case, whose parent temp dir is + removed), or a standalone temp file created directly in the system temp root + (from :func:`materialize_json`, whose file alone is removed -- never its parent). + Only paths under the system temp dir and carrying one of our prefixes are deleted, + as a safety guard. """ + tmp_root = str(Path(tempfile.gettempdir()).resolve()) path = Path(source) + # A standalone temp file we created directly in the temp root (materialize_json): + # remove just the file -- never its parent, which is the shared system temp root. + if path.is_file() and path.name.startswith("flowx-insights-"): + if str(path.resolve()).startswith(tmp_root): + path.unlink(missing_ok=True) + return + # Otherwise the temp dir to remove is the path itself (a mkdtemp dir) or, for the + # single ARM-template case, the file's parent temp dir. target = path if path.is_dir() else path.parent - if str(target.resolve()).startswith(str(Path(tempfile.gettempdir()).resolve())): + if target.name.startswith(_TEMP_DIR_PREFIXES) and str(target.resolve()).startswith(tmp_root): shutil.rmtree(target, ignore_errors=True) diff --git a/src/flowx/mcp/server.py b/src/flowx/mcp/server.py index 36c67fc..ad7230e 100644 --- a/src/flowx/mcp/server.py +++ b/src/flowx/mcp/server.py @@ -492,9 +492,36 @@ def _cmd_install_dashboard(p: dict[str, Any]) -> dict[str, Any]: return {"ok": result.ok, "result": runner.parse_stdout_json(result), "process": result.as_dict()} +def _parse_enrich_violations(stderr: str) -> list[str]: + """Extract the ' - ' lines the adapter's enrich prints on failure.""" + return [line[4:] for line in stderr.splitlines() if line.startswith(" - ")] + + +def _cmd_enrich(p: dict[str, Any]) -> dict[str, Any]: + output_dir = p.get("output_dir", "./flowx_output") + insights = p.get("insights") + insights_path = p.get("insights_path") + if insights is None and not insights_path: + return {"ok": False, "error": "Provide 'insights' (inline dict) or 'insights_path'."} + tmp: str | None = None + try: + if insights is not None: + tmp = runner.materialize_json(insights) + insights_path = tmp + args: list[Any] = ["enrich", "--output-dir", output_dir, "--insights-path", insights_path] + result = runner.run_adapter(args) + out = Path(output_dir) + violations = _parse_enrich_violations(result.stderr) if not result.ok else None + return _phase_result(result, out, inventory=runner.summarize_inventory(out), violations=violations) + finally: + if tmp: + runner.cleanup_materialized(tmp) + + _COMMANDS: dict[str, Callable[[dict[str, Any]], dict[str, Any]]] = { "inputs": _cmd_inputs, "discover": _cmd_discover, + "enrich": _cmd_enrich, "convert": _cmd_convert, "merge_agentic": _cmd_merge_agentic, "resolve_agentic": _cmd_resolve_agentic, @@ -539,18 +566,15 @@ def flowx(command: str, parameters: dict[str, Any] | None = None) -> dict[str, A Airflow reads ``airflow_source_path`` (a DAG .py file or directory). ``package`` is source-independent (it consumes the translation report). - - "inputs": phase(req: "discover"|"convert"|"package"), source(req for discover/convert) — - list a phase's input prompts. - - "discover": source(req), one ADF source key | airflow_source_path (req), output_dir, - pipeline, exclude_dag | exclude_dags (Airflow, repeatable list) — parse and audit definitions. - - "convert": source(req), (one ADF source key | airflow_source_path), output_dir, pipeline, - exclude_dag | exclude_dags (Airflow, repeatable list). - - "merge_agentic": source(req: "adf"), report_path(req), agentic_results_dir(req), output_path — - merge ADF agent results. Airflow's legacy name-based merge is disabled; use resolve_agentic. - - "resolve_agentic": source(req: "airflow"), action(req: prepare | stage | apply), output_dir, - airflow_source_path, report_path, gap_id, candidates, replace, accept_gap | accept_gaps, accept_all, - review_complete, review_manifest, reset — - prepare, stage, and explicitly apply fingerprint-bound Airflow leaf-gap resolutions. + - "inputs": phase(req: "discover"|"convert"|"package") — list a phase's input prompts. + - "discover": one of adf_volume_path | adf_workspace_path | adf_definitions | adf_source_path + (req), output_dir, pipeline — parse ADF JSON, classify activities. + - "enrich": output_dir(req), one of insights(inline dict) | insights_path — validate + merge + agent-authored insights into metadata/inventory.json (returns {ok:false, ...} without writing + on validation failure). + - "convert": output_dir, (adf_volume_path | adf_workspace_path | adf_definitions | + adf_source_path), pipeline. + - "merge_agentic": report_path(req), agentic_results_dir(req), output_path — merge agent results. - "inspect": report_path(req) — return the full translation-option schema (every option with a `show_when` condition) for the agent to walk locally. See "Collecting options" below. - "apply_answers": report_path(req), answers(req, list of "ID=VALUE"), output_dir, lookup_csv. diff --git a/src/flowx/models/adf_ast.py b/src/flowx/models/adf_ast.py index 6047935..05ab369 100644 --- a/src/flowx/models/adf_ast.py +++ b/src/flowx/models/adf_ast.py @@ -297,13 +297,13 @@ def get_pipeline(self, name: str | None) -> AdfPipeline | None: if not name: return None lowered = name.lower() - ci_fallback = None + exact = None for pipeline in self.pipelines: if pipeline.name == name: return pipeline - if ci_fallback is None and pipeline.name.lower() == lowered: - ci_fallback = pipeline - return ci_fallback + if exact is None and pipeline.name.lower() == lowered: + exact = pipeline + return exact # --------------------------------------------------------------------------- @@ -400,3 +400,157 @@ class Lineage: control_edges: list[ControlEdge] = field(default_factory=list) data_edges: list[DataEdge] = field(default_factory=list) + + +# --------------------------------------------------------------------------- +# Agentic insights (discover phase) -- agent-authored judgment merged into +# inventory.json. References pipelines by name. Its cross-pipeline edges are +# either ANNOTATIONS of a deterministic Lineage edge (control/data -- carry no +# facts of their own) or an agent-INFERRED coupling the deterministic layer +# could not see (e.g. data flow that happens inside notebook code, an external +# trigger, a message queue). Inferred edges must cite their evidence and a +# confidence level so they are never mistaken for proven lineage. +# --------------------------------------------------------------------------- + + +@dataclass(slots=True, kw_only=True) +class LineageEdgeRef: + """A typed reference from a PipelineRelationship to one cross-pipeline edge. + + Two tiers: + + * ``"control"`` / ``"data"`` -- an **annotation** of a deterministic edge. + ``edge_identity`` echoes that edge verbatim (``ControlEdge.activity_name`` + for control, ``DataEdge.match_key`` for data) so enrichment can resolve it + against the inventory's ``lineage``. ``evidence`` / ``confidence`` are not + used (the deterministic edge *is* the evidence, confidence is implicitly + high) and must be omitted. + * ``"inferred"`` -- an agent-asserted coupling the deterministic layer did + not find. There is no lineage edge to resolve against, so ``edge_identity`` + is an agent-authored descriptor of what couples the pipelines (e.g. a + shared table or asset name), and ``evidence`` (why the agent believes the + coupling exists) plus ``confidence`` are **required**. This tier stays + pattern-agnostic: it does not encode *why* the deterministic layer missed + the edge, so it generalises to couplings flowx cannot yet see. + + Attributes: + edge_type: The tier -- ``"control"``, ``"data"``, or ``"inferred"``. + edge_identity: For ``"control"`` the ``ControlEdge.activity_name``; for + ``"data"`` the ``DataEdge.match_key`` (both echoed verbatim from a + real edge); for ``"inferred"`` an agent-authored descriptor of the + coupling. + evidence: Inferred edges only -- the observable basis for the asserted + coupling. Required for ``"inferred"``; must be omitted otherwise. + confidence: Inferred edges only -- ``"high"`` / ``"medium"`` / ``"low"``. + Required for ``"inferred"``; must be omitted otherwise. + """ + + edge_type: Literal["control", "data", "inferred"] + edge_identity: str + evidence: str | None = None + confidence: Literal["high", "medium", "low"] | None = None + + +@dataclass(slots=True, kw_only=True) +class RecommendedPattern: + """One ranked Databricks target pattern recommended for a pipeline. + + A pipeline insight carries 1-4 of these, ordered best-first, drawn from the + agent's *holistic* read of the pipeline and grounded in publicly-documented + Databricks capabilities. ``simplification_pattern`` ranks the distinctive + capabilities that collapse a legacy pattern ahead of like-for-like ports and + plain building blocks. + + Attributes: + pattern: The named, publicly-documented Databricks capability (e.g. + ``"Lakeflow Connect SQL Server connector"``). Never an invented name. + fit: One line on why it fits this pipeline / what custom logic it replaces. + simplification_pattern: ``True`` *only* when the pattern uses a **distinctive** + Databricks capability that collapses or eliminates a whole legacy + pattern -- a managed connector (Lakeflow Connect), declarative CDC + (``AUTO CDC``), Auto Loader, or system tables replacing a home-grown + logging tier. ``False`` for a like-for-like port AND for plain native + building blocks that merely re-home the same work (a bare parameterized + Lakeflow Job, a for-each/run-job orchestrator, a plain Delta control + table, ``MERGE INTO``) -- "runs on Databricks" is not a simplification, + so reserve this flag for the capability that makes the old pattern + *disappear*. Rank the ``True`` patterns first. + """ + + pattern: str + fit: str + simplification_pattern: bool + + +@dataclass(slots=True, kw_only=True) +class SystemRecommendation: + """The single top-level architectural decision spanning the whole factory. + + Per-pipeline ``recommended_patterns`` are chosen *under* this decision: the + system-level branch you pick (e.g. adopt a managed connector for an entire + extraction family) cascades into what each pipeline becomes, so it is authored + first and the per-pipeline patterns are kept consistent with it. It captures + the payoff a reader cannot see from any single pipeline card. + + Attributes: + headline: One line naming the decision a migrator must make before any + per-pipeline work (e.g. "Managed ingestion collapses the extraction + factory"). + recommended_patterns: 1-4 whole-system target architectures, ordered + best-first (the simplifying/native branch first), each a + :class:`RecommendedPattern`. ``recommended_patterns[0]`` is the + recommended branch; later entries are the ranked fallbacks. + cascade: What choosing ``recommended_patterns[0]`` collapses or eliminates + across the whole system (e.g. "5 child extractors -> managed connector + pipelines", "version-watermark CSV -> gone"). Empty when the decision + does not cascade. + decision_driver: The gating question that selects the branch (e.g. "Is the + Lakeflow Connect SQL Server connector GA/approved for this source?"); + omit when there is no single deciding factor. + """ + + headline: str + recommended_patterns: list[RecommendedPattern] = field(default_factory=list) + cascade: list[str] = field(default_factory=list) + decision_driver: str | None = None + + +@dataclass(slots=True, kw_only=True) +class PipelineInsight: + """Per-pipeline judgment; references a pipeline by name (foreign key).""" + + pipeline: str + pattern_name: str | None = None + intent: str | None = None + databricks_pattern: str | None = None + recommended_patterns: list[RecommendedPattern] = field(default_factory=list) + conversion_notes: list[str] = field(default_factory=list) + risk_if_ignored: str | None = None + + +@dataclass(slots=True, kw_only=True) +class PipelineRelationship: + """Cross-pipeline judgment. + + Either annotates one deterministic lineage edge (``lineage_edge.edge_type`` + is ``"control"`` / ``"data"``) or records an agent-inferred coupling the + deterministic layer could not see (``"inferred"``). Both endpoints are always + real pipeline names validated against the inventory. + """ + + from_pipeline: str + to_pipeline: str + lineage_edge: LineageEdgeRef + relationship_summary: str | None = None + databricks_pattern: str | None = None + risk_if_ignored: str | None = None + + +@dataclass(slots=True, kw_only=True) +class Insights: + """Agent-authored insights merged into inventory.json under the ``insights`` key.""" + + overview: str | None = None + system_recommendation: SystemRecommendation | None = None + pipeline_insights: list[PipelineInsight] = field(default_factory=list) + pipeline_relationships: list[PipelineRelationship] = field(default_factory=list) diff --git a/src/flowx/parser/lineage.py b/src/flowx/parser/lineage.py index ace9492..72be664 100644 --- a/src/flowx/parser/lineage.py +++ b/src/flowx/parser/lineage.py @@ -195,9 +195,6 @@ def _activity_dataset_refs(activity: AdfActivity, *, produced: bool) -> Iterator def _build_data_edges(definitions: AdfDefinitions) -> list[DataEdge]: - # TODO: the producer x consumer join below is O(producers x consumers). Fine for - # today's factories; if one ever has thousands of same-signature endpoints, bucket - # producers/consumers by (identity or path_signature) and join within buckets. producers: list[_DatasetEndpoint] = [] consumers: list[_DatasetEndpoint] = [] for pipeline in definitions.pipelines: diff --git a/src/flowx/parser/pipeline_insights.py b/src/flowx/parser/pipeline_insights.py new file mode 100644 index 0000000..ab20180 --- /dev/null +++ b/src/flowx/parser/pipeline_insights.py @@ -0,0 +1,371 @@ +"""Validate and merge agent-authored insights into the discover inventory. + +The discover phase writes a pure ``metadata/inventory.json`` (pipelines, summary, +lineage). The agent then *authors* an ``insights`` object -- its judgment about +pipeline intent, Databricks patterns, and cross-pipeline relationships. This +module *enriches* the inventory: it validates the authored JSON against the +inventory and, only when clean, appends the single ``insights`` key while +re-serialising the rest byte-identically. + +A relationship's ``lineage_edge`` comes in two tiers, validated differently: + +* ``control`` / ``data`` -- an **annotation** of a deterministic edge. Its + ``edge_identity`` must resolve to a real edge in the inventory's ``lineage`` + (a ``ControlEdge.activity_name`` or ``DataEdge.match_key``); ``evidence`` / + ``confidence`` must be absent. +* ``inferred`` -- an agent-asserted coupling the deterministic layer never + found (e.g. data flow inside notebook code). There is nothing to resolve + against, so instead the edge must carry a non-empty ``evidence`` string and a + ``confidence`` of ``high`` / ``medium`` / ``low``. Endpoints are still real + pipeline names. This keeps every edge accountable -- annotations to a proven + fact, inferences to stated evidence -- without letting an inference + masquerade as proven lineage. + +There is no LLM here -- the tool only validates and merges. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +_INSIGHTS_TOP_KEYS = {"overview", "system_recommendation", "pipeline_insights", "pipeline_relationships"} +_INSIGHT_KEYS = { + "pipeline", + "pattern_name", + "intent", + "databricks_pattern", + "recommended_patterns", + "conversion_notes", + "risk_if_ignored", +} +_RECOMMENDED_PATTERN_KEYS = {"pattern", "fit", "simplification_pattern"} +_MAX_RECOMMENDED_PATTERNS = 4 +_SYSTEM_RECOMMENDATION_KEYS = {"headline", "recommended_patterns", "cascade", "decision_driver"} +_RELATIONSHIP_KEYS = { + "from_pipeline", + "to_pipeline", + "lineage_edge", + "relationship_summary", + "databricks_pattern", + "risk_if_ignored", +} +_EDGE_KEYS = {"edge_type", "edge_identity", "evidence", "confidence"} +_CONFIDENCE_LEVELS = {"high", "medium", "low"} + + +def _pipeline_names(inventory: dict) -> set[str]: + return {str(p["name"]) for p in inventory.get("pipelines", []) if isinstance(p, dict) and p.get("name") is not None} + + +def _control_edge_triples(inventory: dict) -> set[tuple[str, str, str]]: + """Real control edges as ``(caller_pipeline, callee_pipeline, activity_name)``. + + Resolving on the full triple (not the bare ``activity_name``) is what pins a + relationship to a *specific* edge: ADF names the ExecutePipeline activity + after the callee, so a single ``activity_name`` is shared by every caller of + that callee -- a global-set check would accept a relationship whose + ``from``/``to`` point at the wrong pair. + """ + lineage = inventory.get("lineage") or {} + return { + (str(e["caller_pipeline"]), str(e["callee_pipeline"]), str(e["activity_name"])) + for e in lineage.get("control_edges", []) + if isinstance(e, dict) + and e.get("caller_pipeline") is not None + and e.get("callee_pipeline") is not None + and e.get("activity_name") is not None + } + + +def _data_edge_triples(inventory: dict) -> set[tuple[str, str, str]]: + """Real data edges as ``(producer_pipeline, consumer_pipeline, match_key)``. + + Same rationale as control edges: a ``match_key`` (a shared table/path) can be + produced and consumed across many pipeline pairs, so the producer/consumer + endpoints must match too. A relationship's ``from``/``to`` map to + producer/consumer respectively. + """ + lineage = inventory.get("lineage") or {} + return { + (str(e["producer_pipeline"]), str(e["consumer_pipeline"]), str(e["match_key"])) + for e in lineage.get("data_edges", []) + if isinstance(e, dict) + and e.get("producer_pipeline") is not None + and e.get("consumer_pipeline") is not None + and e.get("match_key") is not None + } + + +def validate_insights(raw: dict, inventory: dict) -> list[str]: + """Validate an authored insights dict against the inventory. + + Returns a list of human-readable violation strings; an empty list means the + insights are valid. All violations are collected (never fail-fast) so the + agent can fix every problem in one pass. + """ + violations: list[str] = [] + if not isinstance(raw, dict): + return [f"insights must be a JSON object, got {type(raw).__name__}"] + + for key in set(raw) - _INSIGHTS_TOP_KEYS: + violations.append(f"unknown top-level key: {key!r}") + + if "system_recommendation" in raw: + violations.extend(_validate_system_recommendation(raw["system_recommendation"])) + + names = _pipeline_names(inventory) + control_triples = _control_edge_triples(inventory) + data_triples = _data_edge_triples(inventory) + + insights = raw.get("pipeline_insights", []) + if not isinstance(insights, list): + violations.append("'pipeline_insights' must be a list") + insights = [] + for i, item in enumerate(insights): + loc = f"pipeline_insights[{i}]" + if not isinstance(item, dict): + violations.append(f"{loc} must be an object") + continue + for key in set(item) - _INSIGHT_KEYS: + violations.append(f"{loc}: unknown field {key!r}") + name = item.get("pipeline") + if not name: + violations.append(f"{loc}: missing required field 'pipeline'") + elif name not in names: + violations.append(f"{loc}: pipeline {name!r} not in inventory") + if "recommended_patterns" in item: + violations.extend(_validate_recommended_patterns(item["recommended_patterns"], loc)) + + relationships = raw.get("pipeline_relationships", []) + if not isinstance(relationships, list): + violations.append("'pipeline_relationships' must be a list") + relationships = [] + for i, rel in enumerate(relationships): + loc = f"pipeline_relationships[{i}]" + if not isinstance(rel, dict): + violations.append(f"{loc} must be an object") + continue + for key in set(rel) - _RELATIONSHIP_KEYS: + violations.append(f"{loc}: unknown field {key!r}") + from_pipeline = rel.get("from_pipeline") + to_pipeline = rel.get("to_pipeline") + for endpoint, value in (("from_pipeline", from_pipeline), ("to_pipeline", to_pipeline)): + if not value: + violations.append(f"{loc}: missing required field {endpoint!r}") + elif value not in names: + violations.append(f"{loc}: {endpoint} {value!r} not in inventory") + violations.extend( + _validate_edge(rel.get("lineage_edge"), loc, from_pipeline, to_pipeline, control_triples, data_triples) + ) + + return violations + + +def _validate_edge( + edge: Any, + loc: str, + from_pipeline: Any, + to_pipeline: Any, + control_triples: set[tuple[str, str, str]], + data_triples: set[tuple[str, str, str]], +) -> list[str]: + """Validate one lineage_edge ref. + + ``control`` / ``data`` edges annotate a deterministic edge: the full + ``(from, to, edge_identity)`` triple must resolve against the inventory's + lineage -- so the annotation connects exactly the pipelines it claims, not + merely some edge that happens to share the ``activity_name`` / ``match_key`` + -- and ``evidence`` / ``confidence`` must be absent. ``inferred`` edges assert + a coupling the deterministic layer never found: nothing to resolve, but a + non-empty ``evidence`` string and a ``confidence`` level are required instead. + """ + if edge is None: + return [f"{loc}: missing required field 'lineage_edge'"] + if not isinstance(edge, dict): + return [f"{loc}.lineage_edge must be an object"] + problems: list[str] = [] + for key in set(edge) - _EDGE_KEYS: + problems.append(f"{loc}.lineage_edge: unknown field {key!r}") + edge_type = edge.get("edge_type") + identity = edge.get("edge_identity") + if edge_type not in ("control", "data", "inferred"): + problems.append(f"{loc}.lineage_edge: edge_type must be 'control', 'data', or 'inferred', got {edge_type!r}") + return problems + if not isinstance(identity, str) or not identity: + problems.append(f"{loc}.lineage_edge: edge_identity must be a non-empty string") + return problems + + if edge_type == "inferred": + problems.extend(_validate_inferred_edge(edge, loc)) + return problems + + # Annotation tier: must resolve to a real edge, and must NOT carry the + # inferred-only evidence/confidence fields. + for field_name in ("evidence", "confidence"): + if edge.get(field_name) is not None: + problems.append(f"{loc}.lineage_edge: {field_name!r} is only valid on an 'inferred' edge") + # Resolve on the full triple. Endpoint problems are already reported above; only + # attempt the lookup when both endpoints are strings, else it is meaningless. + if not isinstance(from_pipeline, str) or not isinstance(to_pipeline, str): + return problems + valid = control_triples if edge_type == "control" else data_triples + if (from_pipeline, to_pipeline, identity) not in valid: + problems.append( + f"{loc}.lineage_edge: {edge_type} edge {identity!r} does not resolve to a lineage edge " + f"from {from_pipeline!r} to {to_pipeline!r}" + ) + return problems + + +def _validate_recommended_patterns(value: Any, loc: str) -> list[str]: + """Validate a pipeline_insight's ``recommended_patterns`` ranked list. + + When present it must hold 1-``_MAX_RECOMMENDED_PATTERNS`` objects, ordered + best-first. Each object requires a non-empty ``pattern`` and ``fit`` string + and a boolean ``simplification_pattern``. All problems are collected. + + Shared by both a pipeline's ``recommended_patterns`` and the top-level + ``system_recommendation.recommended_patterns`` (``loc`` distinguishes them). + """ + field_loc = f"{loc}.recommended_patterns" + if not isinstance(value, list): + return [f"{field_loc} must be a list"] + if not value: + return [ + f"{field_loc} must contain 1-{_MAX_RECOMMENDED_PATTERNS} patterns when present " + f"(omit the field instead of sending an empty list)" + ] + problems: list[str] = [] + if len(value) > _MAX_RECOMMENDED_PATTERNS: + problems.append(f"{field_loc} has {len(value)} patterns; at most {_MAX_RECOMMENDED_PATTERNS} are allowed") + for j, pattern in enumerate(value): + ploc = f"{field_loc}[{j}]" + if not isinstance(pattern, dict): + problems.append(f"{ploc} must be an object") + continue + for key in set(pattern) - _RECOMMENDED_PATTERN_KEYS: + problems.append(f"{ploc}: unknown field {key!r}") + for required in ("pattern", "fit"): + text = pattern.get(required) + if not isinstance(text, str) or not text.strip(): + problems.append(f"{ploc}: {required!r} must be a non-empty string") + # A JSON bool parses to Python bool; reject ints/strings so 1/"yes" don't slip through. + if not isinstance(pattern.get("simplification_pattern"), bool): + problems.append( + f"{ploc}: 'simplification_pattern' must be a boolean (true/false), " + f"got {type(pattern.get('simplification_pattern')).__name__}" + ) + return problems + + +def _validate_system_recommendation(value: Any) -> list[str]: + """Validate the optional top-level ``system_recommendation`` object. + + The one whole-factory architectural decision, authored before per-pipeline + insights. When present it must be an object with a non-empty ``headline`` and + a ``recommended_patterns`` ranked list (validated exactly like a pipeline's -- + the whole-system branches, best-first). ``cascade`` (a list of non-empty + strings naming what the top branch collapses) and ``decision_driver`` (the + gating question) are optional. All problems are collected. + """ + loc = "system_recommendation" + if not isinstance(value, dict): + return [f"{loc} must be an object"] + problems: list[str] = [] + for key in set(value) - _SYSTEM_RECOMMENDATION_KEYS: + problems.append(f"{loc}: unknown field {key!r}") + headline = value.get("headline") + if not isinstance(headline, str) or not headline.strip(): + problems.append(f"{loc}: 'headline' must be a non-empty string") + if "recommended_patterns" not in value: + problems.append(f"{loc}: missing required field 'recommended_patterns'") + else: + problems.extend(_validate_recommended_patterns(value["recommended_patterns"], loc)) + cascade = value.get("cascade") + if cascade is not None and ( + not isinstance(cascade, list) or not all(isinstance(c, str) and c.strip() for c in cascade) + ): + problems.append(f"{loc}: 'cascade' must be a list of non-empty strings when present") + driver = value.get("decision_driver") + if driver is not None and (not isinstance(driver, str) or not driver.strip()): + problems.append(f"{loc}: 'decision_driver' must be a non-empty string when present") + return problems + + +def _validate_inferred_edge(edge: dict, loc: str) -> list[str]: + """Validate the inferred-only fields: non-empty evidence + a confidence level.""" + problems: list[str] = [] + evidence = edge.get("evidence") + if not isinstance(evidence, str) or not evidence.strip(): + problems.append(f"{loc}.lineage_edge: an 'inferred' edge requires a non-empty 'evidence' string") + confidence = edge.get("confidence") + if confidence not in _CONFIDENCE_LEVELS: + problems.append( + f"{loc}.lineage_edge: an 'inferred' edge requires 'confidence' in " + f"{{'high', 'medium', 'low'}}, got {confidence!r}" + ) + return problems + + +def load_insights(*, insights: dict | None = None, insights_path: Path | None = None) -> dict: + """Return the raw insights dict from exactly one source (inline or file). + + Raises: + ValueError: if neither or both sources are provided. + """ + if (insights is None) == (insights_path is None): + raise ValueError("provide exactly one of 'insights' (inline dict) or 'insights_path'") + if insights is not None: + return insights + assert insights_path is not None # guaranteed by the guard above + return json.loads(insights_path.read_text(encoding="utf-8")) + + +def merge_into_inventory(inventory: dict, raw: dict) -> dict: + """Return a new dict identical to *inventory* with one added ``insights`` key. + + Does not mutate the input. No I/O. + """ + merged = dict(inventory) + merged["insights"] = raw + return merged + + +def enrich_inventory( + output_dir: Path, + *, + insights: dict | None = None, + insights_path: Path | None = None, +) -> dict: + """Validate authored insights against the inventory, then merge on success. + + Reads ``/metadata/inventory.json``, validates the authored + insights, and -- only when there are no violations -- writes the merged + inventory back byte-identically (adding just the ``insights`` key). + + Returns ``{"ok", "violations", "pipeline_insights", "relationships"}``. + On violations, ``ok`` is False and the file is left untouched. + + Raises: + FileNotFoundError: when ``inventory.json`` does not exist. + """ + inventory_path = Path(output_dir) / "metadata" / "inventory.json" + if not inventory_path.exists(): + raise FileNotFoundError(f"No inventory.json under {inventory_path.parent}; run discover first.") + inventory = json.loads(inventory_path.read_text(encoding="utf-8")) + + raw = load_insights(insights=insights, insights_path=insights_path) + violations = validate_insights(raw, inventory) + if violations: + return {"ok": False, "violations": violations, "pipeline_insights": 0, "relationships": 0} + + merged = merge_into_inventory(inventory, raw) + inventory_path.write_text(json.dumps(merged, indent=2), encoding="utf-8") + return { + "ok": True, + "violations": [], + "pipeline_insights": len(raw.get("pipeline_insights", [])), + "relationships": len(raw.get("pipeline_relationships", [])), + } diff --git a/src/flowx/reporting/coverage.py b/src/flowx/reporting/coverage.py index 8f15238..298b075 100644 --- a/src/flowx/reporting/coverage.py +++ b/src/flowx/reporting/coverage.py @@ -49,6 +49,7 @@ "finding_fingerprints", "complexity_score", "complexity_size", + "has_insights", ) _CSV_INT_COLUMNS: tuple[str, ...] = ( @@ -122,6 +123,7 @@ def build_coverage_rows(metadata_dir: Path) -> list[dict[str, Any]]: for row in csv.DictReader(handle): csv_by_pipeline[row["pipeline"]] = row + has_insights = "insights" in inventory rows: list[dict[str, Any]] = [] for pipeline in inventory.get("pipelines", []): name = pipeline.get("name", "") @@ -204,6 +206,7 @@ def _csv_int(col: str, _csv_row: dict[str, str] = csv_row) -> int: "finding_fingerprints": json.dumps(fingerprints, separators=(",", ":")), "complexity_score": _csv_int("complexity_score"), "complexity_size": csv_row.get("complexity_size", "") or "", + "has_insights": has_insights, } ) rows.sort(key=lambda row: row["pipeline"]) diff --git a/src/flowx/reporting/results.py b/src/flowx/reporting/results.py index 7ec5f50..fdcc1a4 100644 --- a/src/flowx/reporting/results.py +++ b/src/flowx/reporting/results.py @@ -48,6 +48,7 @@ "finding_fingerprints": "STRING", "complexity_score": "INT", "complexity_size": "STRING", + "has_insights": "BOOLEAN", } RESULTS_COLUMNS: tuple[tuple[str, str], ...] = ( @@ -57,18 +58,8 @@ *((col, _METRIC_SQL_TYPES[col]) for col in COVERAGE_METRIC_COLUMNS), ) -_STRING_METRICS: frozenset[str] = frozenset( - { - "pipeline", - "agentic_resolution_outcomes", - "agentic_provider_version", - "reconciliation_status", - "migration_status", - "finding_fingerprints", - "complexity_size", - } -) -_FLOAT_METRICS: frozenset[str] = frozenset({"coverage_pct", "deterministic_coverage_pct", "code_attached_coverage_pct"}) +_STRING_METRICS: frozenset[str] = frozenset({"pipeline", "complexity_size"}) +_BOOL_METRICS: frozenset[str] = frozenset({"has_insights"}) def _sql_str(value: Any) -> str: @@ -80,7 +71,9 @@ def _metric_value_sql(column: str, value: Any) -> str: """Renders one metric column value as a SQL literal.""" if column in _STRING_METRICS: return _sql_str(value) - if column in _FLOAT_METRICS: + if column in _BOOL_METRICS: + return "TRUE" if value else "FALSE" + if column == "coverage_pct": return repr(float(value or 0)) return str(int(value or 0)) diff --git a/src/flowx/sources/adf/loader.py b/src/flowx/sources/adf/loader.py index b14e365..22470e3 100644 --- a/src/flowx/sources/adf/loader.py +++ b/src/flowx/sources/adf/loader.py @@ -1122,8 +1122,8 @@ def main(argv: list[str] | None = None) -> int: logger.info("Wrote %d pipeline ARM JSON file(s) to %s", len(arm_paths), metadata_dir) summary = inventory_dict["summary"] - print("\nADF Profile Summary") - print("===================") + print("\nADF Discovery Summary") + print("=====================") print(f"Pipelines parsed: {summary['pipeline_count']}") print(f"Total activities: {summary['activity_count']}") print("\nStrategy Breakdown:") diff --git a/src/flowx/sources/adf/translators/execute_pipeline.py b/src/flowx/sources/adf/translators/execute_pipeline.py index 82f456a..dbfcf8b 100644 --- a/src/flowx/sources/adf/translators/execute_pipeline.py +++ b/src/flowx/sources/adf/translators/execute_pipeline.py @@ -8,7 +8,7 @@ from flowx.models.ir import Activity, ExecutePipelineActivity, TranslationContext from flowx.parser.expression_parser import resolve_expression from flowx.parser.lineage import read_execute_pipeline_ref -from flowx.sources.adf.translators.resolve import resolve_field +from flowx.translator.activity_translators.resolve import resolve_field def translate( diff --git a/tests/unit/test_lineage.py b/tests/unit/test_lineage.py index de93397..f72b43c 100644 --- a/tests/unit/test_lineage.py +++ b/tests/unit/test_lineage.py @@ -90,11 +90,6 @@ def test_defaults_wait_true_and_empty_name(self): act = AdfActivity(name="Run", type="ExecutePipeline", type_properties={}) assert read_execute_pipeline_ref(act) == ("", True) - def test_non_dict_pipeline_ref_is_stringified(self): - # ADF normally exports a dict ref, but a bare string must not crash the reader. - act = AdfActivity(name="Run", type="ExecutePipeline", type_properties={"pipeline": "child"}) - assert read_execute_pipeline_ref(act) == ("child", True) - class TestDataEdges: def _delta_dataset(self, name: str, table: str) -> AdfDataset: diff --git a/tests/unit/test_pipeline_insights.py b/tests/unit/test_pipeline_insights.py new file mode 100644 index 0000000..c8f628e --- /dev/null +++ b/tests/unit/test_pipeline_insights.py @@ -0,0 +1,843 @@ +"""Tests for agentic insights models, validation, and enrichment (discover phase).""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from flowx.adapter.__main__ import main as adapter_cli_main +from flowx.mcp import runner as mcp_runner +from flowx.mcp.server import _cmd_enrich +from flowx.models.adf_ast import ( + Insights, + LineageEdgeRef, + PipelineInsight, + PipelineRelationship, + RecommendedPattern, + SystemRecommendation, +) +from flowx.parser.adf_loader import ( + _inventory_to_dict, + build_inventory, + load_adf_definitions, +) +from flowx.parser.pipeline_insights import ( + enrich_inventory, + load_insights, + merge_into_inventory, + validate_insights, +) + + +def test_insights_dataclasses_construct_with_defaults(): + edge = LineageEdgeRef(edge_type="control", edge_identity="Run Ingestion Pipeline") + rel = PipelineRelationship(from_pipeline="factory_a", to_pipeline="factory_b", lineage_edge=edge) + insight = PipelineInsight(pipeline="factory_a") + doc = Insights( + overview="whole factory", + pipeline_insights=[insight], + pipeline_relationships=[rel], + ) + assert doc.pipeline_insights[0].pipeline == "factory_a" + assert doc.pipeline_relationships[0].lineage_edge.edge_type == "control" + assert doc.pipeline_relationships[0].lineage_edge.edge_identity == "Run Ingestion Pipeline" + # optional fields default cleanly + assert insight.recommended_patterns == [] + assert insight.conversion_notes == [] + assert insight.risk_if_ignored is None + assert rel.relationship_summary is None + + +def test_recommended_pattern_dataclass_defaults(): + pat = RecommendedPattern( + pattern="Lakeflow Connect SQL Server connector", + fit="Managed CDC ingestion replaces the bespoke watermark Copy", + simplification_pattern=True, + ) + assert pat.pattern == "Lakeflow Connect SQL Server connector" + assert pat.simplification_pattern is True + + +def test_system_recommendation_dataclass_defaults(): + sr = SystemRecommendation(headline="Managed ingestion collapses the extraction factory") + assert sr.headline.startswith("Managed ingestion") + assert sr.recommended_patterns == [] + assert sr.cascade == [] + assert sr.decision_driver is None + + +def _inventory() -> dict: + """A minimal inventory dict in discover's serialized shape.""" + return { + "source_dir": "/tmp/adf", + "pipelines": [ + {"name": "factory_a", "activities": []}, + {"name": "factory_b", "activities": []}, + ], + "summary": {"pipeline_count": 2}, + "lineage": { + "control_edges": [ + { + "caller_pipeline": "factory_a", + "callee_pipeline": "factory_b", + "activity_name": "Run Ingestion Pipeline", + "wait_on_completion": True, + } + ], + "data_edges": [ + { + "dataset_name": "ds_orders", + "identity": "curated.orders", + "producer_pipeline": "factory_a", + "producer_activity": "Write Orders", + "consumer_pipeline": "factory_b", + "consumer_activity": "Read Orders", + "match_kind": "identity", + "match_key": "curated.orders", + } + ], + }, + } + + +def _good_insights() -> dict: + return { + "overview": "Two-stage ingestion then transform.", + "pipeline_insights": [ + { + "pipeline": "factory_a", + "intent": "Ingest", + "databricks_pattern": "Autoloader", + "recommended_patterns": [ + { + "pattern": "Lakeflow Connect SQL Server connector", + "fit": "Managed CDC ingestion replaces the bespoke watermark Copy", + "simplification_pattern": True, + }, + { + "pattern": "Auto Loader", + "fit": "Incremental file ingestion when a managed connector is unavailable", + "simplification_pattern": False, + }, + ], + "risk_if_ignored": "Switch-nested calls read as a leaf in lineage", + }, + {"pipeline": "factory_b", "intent": "Transform"}, + ], + "pipeline_relationships": [ + { + "from_pipeline": "factory_a", + "to_pipeline": "factory_b", + "lineage_edge": { + "edge_type": "control", + "edge_identity": "Run Ingestion Pipeline", + }, + "relationship_summary": "A invokes B", + "databricks_pattern": "run_job_task", + "risk_if_ignored": "ordering lost", + } + ], + } + + +def test_validator_accepts_good_insights(): + assert validate_insights(_good_insights(), _inventory()) == [] + + +def test_rejects_pipeline_not_in_inventory(): + raw = _good_insights() + raw["pipeline_insights"][0]["pipeline"] = "ghost_pipeline" + violations = validate_insights(raw, _inventory()) + assert violations + assert any("ghost_pipeline" in v for v in violations) + + +def test_rejects_relationship_endpoint_not_in_inventory(): + raw = _good_insights() + raw["pipeline_relationships"][0]["to_pipeline"] = "ghost_pipeline" + violations = validate_insights(raw, _inventory()) + assert any("ghost_pipeline" in v for v in violations) + + +def test_rejects_unresolvable_control_edge(): + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"]["edge_identity"] = "No Such Activity" + violations = validate_insights(raw, _inventory()) + assert any("No Such Activity" in v for v in violations) + + +def test_data_edge_binds_on_match_key(): + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"] = { + "edge_type": "data", + "edge_identity": "curated.orders", + } + assert validate_insights(raw, _inventory()) == [] + # a non-matching key is rejected + raw["pipeline_relationships"][0]["lineage_edge"]["edge_identity"] = "curated.missing" + assert validate_insights(raw, _inventory()) + + +def test_inferred_edge_with_evidence_and_confidence_validates(): + """An inferred edge needs no deterministic edge to resolve against -- just + a real endpoint pair, a non-empty evidence string, and a confidence level.""" + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"] = { + "edge_type": "inferred", + "edge_identity": "curated.orders_enriched", + "evidence": "factory_a's notebook writes curated.orders_enriched; factory_b's notebook reads it.", + "confidence": "medium", + } + assert validate_insights(raw, _inventory()) == [] + + +def test_inferred_edge_requires_evidence(): + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"] = { + "edge_type": "inferred", + "edge_identity": "curated.orders_enriched", + "confidence": "low", + } + violations = validate_insights(raw, _inventory()) + assert any("evidence" in v for v in violations) + + +def test_inferred_edge_requires_valid_confidence(): + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"] = { + "edge_type": "inferred", + "edge_identity": "curated.orders_enriched", + "evidence": "shared table observed in both notebooks", + "confidence": "pretty-sure", + } + violations = validate_insights(raw, _inventory()) + assert any("confidence" in v for v in violations) + # missing confidence entirely is also rejected + del raw["pipeline_relationships"][0]["lineage_edge"]["confidence"] + assert any("confidence" in v for v in validate_insights(raw, _inventory())) + + +def test_inferred_edge_does_not_resolve_against_lineage(): + """An inferred edge_identity is agent-authored, not a real edge key, so it must + NOT be validated against the deterministic lineage sets.""" + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"] = { + "edge_type": "inferred", + # deliberately not a real control activity_name or data match_key + "edge_identity": "not-a-real-lineage-key", + "evidence": "coupling inferred from shared notebook output path", + "confidence": "high", + } + assert validate_insights(raw, _inventory()) == [] + + +def test_annotation_edge_rejects_evidence_confidence(): + """evidence/confidence are inferred-only; a control/data edge carrying them is rejected.""" + raw = _good_insights() + raw["pipeline_relationships"][0]["lineage_edge"]["evidence"] = "should not be here" + raw["pipeline_relationships"][0]["lineage_edge"]["confidence"] = "high" + violations = validate_insights(raw, _inventory()) + assert any("evidence" in v for v in violations) + assert any("confidence" in v for v in violations) + + +def test_control_edge_resolves_on_full_triple_not_just_activity_name(): + """A control edge_identity that is a real activity_name but belongs to a + DIFFERENT (caller, callee) pair must be rejected. + + ADF names the ExecutePipeline activity after the callee, so one activity_name + is shared by every caller of that callee; resolving on the bare name (a global + set) would wrongly accept a relationship whose from/to point at another pair. + """ + inventory = { + "pipelines": [ + {"name": "orchestrator_a", "activities": []}, + {"name": "orchestrator_b", "activities": []}, + {"name": "shared_callee", "activities": []}, + ], + "summary": {"pipeline_count": 3}, + "lineage": { + "control_edges": [ + { + "caller_pipeline": "orchestrator_a", + "callee_pipeline": "shared_callee", + "activity_name": "Run Shared", + "wait_on_completion": True, + }, + { + "caller_pipeline": "orchestrator_b", + "callee_pipeline": "shared_callee", + "activity_name": "Run Shared", # same name, different caller + "wait_on_completion": True, + }, + ], + "data_edges": [], + }, + } + # Real edge: orchestrator_a -> shared_callee with "Run Shared" resolves. + good = { + "pipeline_insights": [], + "pipeline_relationships": [ + { + "from_pipeline": "orchestrator_a", + "to_pipeline": "shared_callee", + "lineage_edge": {"edge_type": "control", "edge_identity": "Run Shared"}, + } + ], + } + assert validate_insights(good, inventory) == [] + + # Wrong pair: no edge orchestrator_a -> orchestrator_b exists, even though the + # activity_name "Run Shared" is a real name elsewhere. Must be rejected. + bad = json.loads(json.dumps(good)) + bad["pipeline_relationships"][0]["to_pipeline"] = "orchestrator_b" + violations = validate_insights(bad, inventory) + assert violations, "a valid activity_name on the wrong (from,to) pair must not resolve" + assert any("orchestrator_b" in v for v in violations) + + # Right pair, wrong identity: the edge orchestrator_a -> shared_callee is real, + # but "Nope" is not its activity_name. Must be rejected. + wrong_id = json.loads(json.dumps(good)) + wrong_id["pipeline_relationships"][0]["lineage_edge"]["edge_identity"] = "Nope" + assert validate_insights(wrong_id, inventory) + + # Reversed direction: the real edge is caller=orchestrator_a -> callee=shared_callee. + # Swapping from/to is NOT a real edge and must be rejected -- pins the non-reversed + # from->caller / to->callee mapping so a future refactor cannot silently flip it. + reversed_rel = json.loads(json.dumps(good)) + reversed_rel["pipeline_relationships"][0]["from_pipeline"] = "shared_callee" + reversed_rel["pipeline_relationships"][0]["to_pipeline"] = "orchestrator_a" + assert validate_insights(reversed_rel, inventory), "reversed-direction edge must not resolve" + + +def test_data_edge_resolves_on_full_triple_not_just_match_key(): + """Two producers write the same match_key. A relationship must resolve only to + the producer/consumer pair that actually exists, not to any edge with that key.""" + inventory = { + "pipelines": [ + {"name": "producer_p", "activities": []}, + {"name": "producer_q", "activities": []}, + {"name": "consumer_c", "activities": []}, + ], + "summary": {"pipeline_count": 3}, + "lineage": { + "control_edges": [], + "data_edges": [ + { + "dataset_name": "ds", + "identity": "curated.shared", + "producer_pipeline": "producer_p", + "producer_activity": "Write", + "consumer_pipeline": "consumer_c", + "consumer_activity": "Read", + "match_kind": "identity", + "match_key": "curated.shared", + }, + { + "dataset_name": "ds", + "identity": "curated.shared", + "producer_pipeline": "producer_q", # same key, different producer + "producer_activity": "Write", + "consumer_pipeline": "consumer_c", + "consumer_activity": "Read", + "match_kind": "identity", + "match_key": "curated.shared", + }, + ], + }, + } + good = { + "pipeline_insights": [], + "pipeline_relationships": [ + { + "from_pipeline": "producer_q", + "to_pipeline": "consumer_c", + "lineage_edge": {"edge_type": "data", "edge_identity": "curated.shared"}, + } + ], + } + assert validate_insights(good, inventory) == [] + + # producer_p -> producer_q is NOT a real edge, though both know curated.shared. + bad = json.loads(json.dumps(good)) + bad["pipeline_relationships"][0]["from_pipeline"] = "producer_p" + bad["pipeline_relationships"][0]["to_pipeline"] = "producer_q" + assert validate_insights(bad, inventory) + + +def test_rejects_missing_required_field(): + # PipelineInsight missing 'pipeline' + raw = {"pipeline_insights": [{"intent": "x"}], "pipeline_relationships": []} + assert any("pipeline" in v for v in validate_insights(raw, _inventory())) + # PipelineRelationship missing 'lineage_edge' + raw2 = { + "pipeline_insights": [], + "pipeline_relationships": [{"from_pipeline": "factory_a", "to_pipeline": "factory_b"}], + } + assert any("lineage_edge" in v for v in validate_insights(raw2, _inventory())) + + +def test_rejects_unknown_field(): + raw = _good_insights() + raw["pipeline_insights"][0]["bogus_key"] = "x" + assert any("bogus_key" in v for v in validate_insights(raw, _inventory())) + + +def test_rejects_unknown_top_level_key(): + raw = _good_insights() + raw["surprise"] = 1 + assert any("surprise" in v for v in validate_insights(raw, _inventory())) + + +# --- recommended_patterns ------------------------------------------------- + + +def _set_patterns(raw: dict, patterns: object) -> dict: + """Set factory_a's recommended_patterns to *patterns* and return the dict.""" + raw["pipeline_insights"][0]["recommended_patterns"] = patterns + return raw + + +def test_recommended_patterns_optional_when_omitted(): + raw = _good_insights() + del raw["pipeline_insights"][0]["recommended_patterns"] + assert validate_insights(raw, _inventory()) == [] + + +def test_recommended_patterns_accepts_one_to_four(): + one = [{"pattern": "Lakeflow Jobs", "fit": "orchestration", "simplification_pattern": True}] + assert validate_insights(_set_patterns(_good_insights(), one), _inventory()) == [] + four = [{"pattern": f"Pattern {n}", "fit": f"reason {n}", "simplification_pattern": n % 2 == 0} for n in range(4)] + assert validate_insights(_set_patterns(_good_insights(), four), _inventory()) == [] + + +def test_recommended_patterns_rejects_more_than_four(): + five = [{"pattern": f"Pattern {n}", "fit": f"reason {n}", "simplification_pattern": True} for n in range(5)] + violations = validate_insights(_set_patterns(_good_insights(), five), _inventory()) + assert any("recommended_patterns" in v for v in violations) + + +def test_recommended_patterns_rejects_empty_list(): + violations = validate_insights(_set_patterns(_good_insights(), []), _inventory()) + assert any("recommended_patterns" in v for v in violations) + + +def test_recommended_patterns_rejects_non_list(): + violations = validate_insights(_set_patterns(_good_insights(), "Lakeflow Jobs"), _inventory()) + assert any("recommended_patterns" in v and "list" in v for v in violations) + + +def test_recommended_patterns_rejects_non_dict_item(): + violations = validate_insights(_set_patterns(_good_insights(), ["Lakeflow Jobs"]), _inventory()) + assert any("recommended_patterns[0]" in v for v in violations) + + +def test_recommended_patterns_requires_pattern_and_fit(): + missing_pattern = [{"fit": "x", "simplification_pattern": True}] + assert any( + "pattern" in v for v in validate_insights(_set_patterns(_good_insights(), missing_pattern), _inventory()) + ) + missing_fit = [{"pattern": "Lakeflow Jobs", "simplification_pattern": True}] + assert any("fit" in v for v in validate_insights(_set_patterns(_good_insights(), missing_fit), _inventory())) + blank_pattern = [{"pattern": " ", "fit": "x", "simplification_pattern": True}] + assert any("pattern" in v for v in validate_insights(_set_patterns(_good_insights(), blank_pattern), _inventory())) + + +def test_recommended_patterns_simplification_pattern_must_be_bool(): + bad = [{"pattern": "Lakeflow Jobs", "fit": "x", "simplification_pattern": "yes"}] + violations = validate_insights(_set_patterns(_good_insights(), bad), _inventory()) + assert any("simplification_pattern" in v for v in violations) + # missing entirely is also rejected (the field is required) + missing = [{"pattern": "Lakeflow Jobs", "fit": "x"}] + assert any( + "simplification_pattern" in v for v in validate_insights(_set_patterns(_good_insights(), missing), _inventory()) + ) + + +def test_recommended_patterns_effort_field_removed(): + """`effort` was dropped from the schema; it must now be rejected as an unknown field.""" + bad = [{"pattern": "Lakeflow Jobs", "fit": "x", "simplification_pattern": True, "effort": "moderate"}] + violations = validate_insights(_set_patterns(_good_insights(), bad), _inventory()) + assert any("effort" in v for v in violations) + + +def test_recommended_patterns_rejects_unknown_item_field(): + bad = [{"pattern": "Lakeflow Jobs", "fit": "x", "simplification_pattern": True, "bogus": 1}] + violations = validate_insights(_set_patterns(_good_insights(), bad), _inventory()) + assert any("bogus" in v for v in violations) + + +# --- system_recommendation ------------------------------------------------ + + +def _good_system_recommendation() -> dict: + return { + "headline": "Managed ingestion collapses the extraction factory", + "recommended_patterns": [ + { + "pattern": "Lakeflow Connect for the whole SQL Server extraction family", + "fit": "One managed connector replaces the fan-out orchestrator, the clones, and the watermark CSV", + "simplification_pattern": True, + }, + { + "pattern": "For-each orchestrator + collapsed parameterized jobs", + "fit": "Fallback when the connector is not approved for this source", + "simplification_pattern": False, + }, + ], + "cascade": [ + "clone extractors -> managed connector pipelines", + "version-watermark CSV -> gone", + ], + "decision_driver": "Is the Lakeflow Connect SQL Server connector GA/approved for this source?", + } + + +def test_system_recommendation_optional_when_omitted(): + raw = _good_insights() + assert "system_recommendation" not in raw + assert validate_insights(raw, _inventory()) == [] + + +def test_system_recommendation_accepts_good(): + raw = _good_insights() + raw["system_recommendation"] = _good_system_recommendation() + assert validate_insights(raw, _inventory()) == [] + + +def test_system_recommendation_must_be_object(): + raw = _good_insights() + raw["system_recommendation"] = "nope" + assert any("system_recommendation" in v for v in validate_insights(raw, _inventory())) + + +def test_system_recommendation_requires_headline(): + raw = _good_insights() + sr = _good_system_recommendation() + del sr["headline"] + raw["system_recommendation"] = sr + assert any("headline" in v for v in validate_insights(raw, _inventory())) + + +def test_system_recommendation_requires_recommended_patterns(): + raw = _good_insights() + sr = _good_system_recommendation() + del sr["recommended_patterns"] + raw["system_recommendation"] = sr + assert any("recommended_patterns" in v for v in validate_insights(raw, _inventory())) + + +def test_system_recommendation_reuses_pattern_validation(): + """The branch patterns go through the same validator, scoped under system_recommendation.""" + raw = _good_insights() + sr = _good_system_recommendation() + sr["recommended_patterns"][0]["simplification_pattern"] = "yes" # must be a bool + raw["system_recommendation"] = sr + violations = validate_insights(raw, _inventory()) + assert any("simplification_pattern" in v and "system_recommendation" in v for v in violations) + + +def test_system_recommendation_rejects_unknown_field(): + raw = _good_insights() + sr = _good_system_recommendation() + sr["bogus"] = 1 + raw["system_recommendation"] = sr + assert any("bogus" in v for v in validate_insights(raw, _inventory())) + + +def test_system_recommendation_cascade_must_be_list_of_strings(): + raw = _good_insights() + sr = _good_system_recommendation() + sr["cascade"] = "not a list" + raw["system_recommendation"] = sr + assert any("cascade" in v for v in validate_insights(raw, _inventory())) + + +def test_system_recommendation_survives_enrich_round_trip(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + raw = _good_insights() + raw["system_recommendation"] = _good_system_recommendation() + result = enrich_inventory(tmp_path, insights=raw) + assert result["ok"] is True + on_disk = json.loads((tmp_path / "metadata" / "inventory.json").read_text()) + assert on_disk["insights"]["system_recommendation"]["headline"].startswith("Managed ingestion") + + +def _write_inventory(tmp_path: Path, inventory: dict) -> Path: + """Write inventory.json exactly as discover does (indent=2, no trailing newline).""" + metadata = tmp_path / "metadata" + metadata.mkdir(parents=True, exist_ok=True) + path = metadata / "inventory.json" + path.write_text(json.dumps(inventory, indent=2), encoding="utf-8") + return path + + +def test_load_insights_requires_exactly_one_source(): + with pytest.raises(ValueError): + load_insights() + with pytest.raises(ValueError): + load_insights(insights={"a": 1}, insights_path=Path("/x")) + + +def test_load_insights_from_inline_dict(): + assert load_insights(insights={"overview": "x"}) == {"overview": "x"} + + +def test_load_insights_from_path(tmp_path: Path): + p = tmp_path / "ins.json" + p.write_text(json.dumps({"overview": "y"}), encoding="utf-8") + assert load_insights(insights_path=p) == {"overview": "y"} + + +def test_merge_into_inventory_adds_one_key_without_mutating(): + inv = {"pipelines": [], "summary": {}, "lineage": {}} + raw = {"overview": "z"} + merged = merge_into_inventory(inv, raw) + assert merged["insights"] == {"overview": "z"} + assert "insights" not in inv # input not mutated + assert set(merged) == {"pipelines", "summary", "lineage", "insights"} + + +def test_enrich_success_counts_and_writes(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + result = enrich_inventory(tmp_path, insights=_good_insights()) + assert result["ok"] is True + assert result["violations"] == [] + assert result["pipeline_insights"] == 2 + assert result["relationships"] == 1 + on_disk = json.loads((tmp_path / "metadata" / "inventory.json").read_text()) + assert on_disk["insights"]["overview"] == "Two-stage ingestion then transform." + + +def test_two_pass_deterministic_keys_byte_identical(tmp_path: Path): + path = _write_inventory(tmp_path, _inventory()) + before = path.read_text(encoding="utf-8") + enrich_inventory(tmp_path, insights=_good_insights()) + after = json.loads(path.read_text(encoding="utf-8")) + # every key except the added 'insights' is byte-identical to the pre-enrich file + after_without_insights = {k: v for k, v in after.items() if k != "insights"} + assert json.dumps(after_without_insights, indent=2) == before + + +def test_enrich_is_idempotent(tmp_path: Path): + path = _write_inventory(tmp_path, _inventory()) + enrich_inventory(tmp_path, insights=_good_insights()) + first = path.read_text(encoding="utf-8") + enrich_inventory(tmp_path, insights=_good_insights()) + second = path.read_text(encoding="utf-8") + assert first == second + + +def test_validation_failure_does_not_write(tmp_path: Path): + path = _write_inventory(tmp_path, _inventory()) + before = path.read_text(encoding="utf-8") + bad = _good_insights() + bad["pipeline_insights"][0]["pipeline"] = "ghost_pipeline" + result = enrich_inventory(tmp_path, insights=bad) + assert result["ok"] is False + assert result["violations"] + assert path.read_text(encoding="utf-8") == before # file untouched + + +def test_enrich_missing_inventory_raises(tmp_path: Path): + with pytest.raises(FileNotFoundError): + enrich_inventory(tmp_path, insights=_good_insights()) + + +def _real_inventory(fixtures_dir) -> dict: + """Build a real inventory dict (with lineage) from the shipped fixtures.""" + definitions = load_adf_definitions(fixtures_dir) + inventory = build_inventory(definitions) + return _inventory_to_dict(inventory, str(fixtures_dir)) + + +def test_control_edge_binding_matches_and_rejects(fixtures_dir): + inventory = _real_inventory(fixtures_dir) + good = { + "pipeline_insights": [], + "pipeline_relationships": [ + { + "from_pipeline": "pipeline_execute_pipeline_nested", + "to_pipeline": "pipeline_copy_sql_to_delta", + "lineage_edge": { + "edge_type": "control", + "edge_identity": "Run Ingestion Pipeline", + }, + } + ], + } + assert validate_insights(good, inventory) == [] + + bad = json.loads(json.dumps(good)) + bad["pipeline_relationships"][0]["lineage_edge"]["edge_identity"] = "No Such Activity" + assert validate_insights(bad, inventory) + + +def test_real_inventory_enrich_round_trip(fixtures_dir, tmp_path: Path): + inventory = _real_inventory(fixtures_dir) + metadata = tmp_path / "metadata" + metadata.mkdir(parents=True) + (metadata / "inventory.json").write_text(json.dumps(inventory, indent=2), encoding="utf-8") + result = enrich_inventory( + tmp_path, + insights={ + "overview": "orchestrated ingest/transform/cleanup", + "pipeline_insights": [{"pipeline": "pipeline_execute_pipeline_nested", "intent": "orchestrate"}], + "pipeline_relationships": [], + }, + ) + assert result["ok"] is True + on_disk = json.loads((metadata / "inventory.json").read_text()) + assert on_disk["insights"]["pipeline_insights"][0]["pipeline"] == "pipeline_execute_pipeline_nested" + + +def test_adapter_enrich_success(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + ins = tmp_path / "insights.json" + ins.write_text(json.dumps(_good_insights()), encoding="utf-8") + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path), "--insights-path", str(ins)]) + assert code == 0 + on_disk = json.loads((tmp_path / "metadata" / "inventory.json").read_text()) + assert "insights" in on_disk + + +def test_adapter_enrich_validation_failure_returns_1(tmp_path: Path): + path = _write_inventory(tmp_path, _inventory()) + before = path.read_text(encoding="utf-8") + bad = _good_insights() + bad["pipeline_insights"][0]["pipeline"] = "ghost_pipeline" + ins = tmp_path / "insights.json" + ins.write_text(json.dumps(bad), encoding="utf-8") + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path), "--insights-path", str(ins)]) + assert code == 1 + assert path.read_text(encoding="utf-8") == before # untouched + + +def test_adapter_enrich_missing_inventory_returns_1(tmp_path: Path): + ins = tmp_path / "insights.json" + ins.write_text(json.dumps(_good_insights()), encoding="utf-8") + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path), "--insights-path", str(ins)]) + assert code == 1 + + +def test_adapter_enrich_inline_json_string(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path), "--insights", json.dumps(_good_insights())]) + assert code == 0 + assert "insights" in json.loads((tmp_path / "metadata" / "inventory.json").read_text()) + + +def test_materialize_json_round_trips(tmp_path: Path): + path = mcp_runner.materialize_json({"overview": "x"}) + try: + assert json.loads(Path(path).read_text()) == {"overview": "x"} + finally: + mcp_runner.cleanup_materialized(path) + assert not Path(path).exists() + + +def test_cmd_enrich_requires_a_payload(): + result = _cmd_enrich({"output_dir": "./flowx_output"}) + assert result["ok"] is False + assert "insights" in result["error"] + + +def test_cmd_enrich_inline_dict_success(tmp_path: Path): + _write_inventory(tmp_path, _inventory()) + result = _cmd_enrich({"output_dir": str(tmp_path), "insights": _good_insights()}) + assert result["ok"] is True + assert "insights" in json.loads((tmp_path / "metadata" / "inventory.json").read_text()) + + +def test_validate_insights_rejects_non_dict(): + """validate_insights must reject non-dict inputs with an actionable violation.""" + violations_none = validate_insights(None, _inventory()) # type: ignore[arg-type] + assert violations_none, "expected violations for None input" + assert any("JSON object" in v or "NoneType" in v for v in violations_none) + + violations_list = validate_insights([], _inventory()) # type: ignore[arg-type] + assert violations_list, "expected violations for list input" + assert any("JSON object" in v or "list" in v for v in violations_list) + + +def test_adapter_enrich_rejects_neither_source(tmp_path: Path): + """enrich must return 1 when neither --insights nor --insights-path is given.""" + _write_inventory(tmp_path, _inventory()) + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path)]) + assert code == 1 + + +def test_adapter_enrich_rejects_both_sources(tmp_path: Path): + """enrich must return 1 when both --insights and --insights-path are given.""" + _write_inventory(tmp_path, _inventory()) + ins_file = tmp_path / "insights.json" + ins_file.write_text(json.dumps(_good_insights()), encoding="utf-8") + code = adapter_cli_main( + [ + "enrich", + "--output-dir", + str(tmp_path), + "--insights", + json.dumps(_good_insights()), + "--insights-path", + str(ins_file), + ] + ) + assert code == 1 + + +def test_adapter_enrich_rejects_malformed_inline_json(tmp_path: Path): + """enrich must return 1 and not write when --insights is not valid JSON.""" + path = _write_inventory(tmp_path, _inventory()) + before = path.read_text(encoding="utf-8") + code = adapter_cli_main(["enrich", "--output-dir", str(tmp_path), "--insights", "{not valid json"]) + assert code == 1 + assert path.read_text(encoding="utf-8") == before # inventory untouched + + +def test_cmd_enrich_failure_surfaces_structured_violations(tmp_path: Path): + """On a validation failure, _cmd_enrich must return ok:false with a non-empty violations list.""" + _write_inventory(tmp_path, _inventory()) + bad = _good_insights() + bad["pipeline_insights"][0]["pipeline"] = "ghost_pipeline" + result = _cmd_enrich({"output_dir": str(tmp_path), "insights": bad}) + assert result["ok"] is False + violations = result.get("violations") + assert violations, "expected a non-empty violations list on failure" + assert any("ghost_pipeline" in v for v in violations) + + +def test_cmd_enrich_success_has_no_violations(tmp_path: Path): + """On a successful enrich, _cmd_enrich must not carry stray violations.""" + _write_inventory(tmp_path, _inventory()) + result = _cmd_enrich({"output_dir": str(tmp_path), "insights": _good_insights()}) + assert result["ok"] is True + assert not result.get("violations") + + +def test_cleanup_materialized_handles_all_prefixes(tmp_path: Path): + import tempfile as _tempfile + from pathlib import Path as _Path + + # mkdtemp dirs for all three prefixes are removed + for prefix in ("flowx-adf-", "flowx-vol-", "flowx-ws-"): + d = _tempfile.mkdtemp(prefix=prefix) + assert _Path(d).is_dir() + mcp_runner.cleanup_materialized(d) + assert not _Path(d).exists() + + # single ARM-template file inside a flowx-adf- dir removes the parent dir + base = _tempfile.mkdtemp(prefix="flowx-adf-") + arm = _Path(base) / "arm_template.json" + arm.write_text("{}", encoding="utf-8") + mcp_runner.cleanup_materialized(str(arm)) + assert not _Path(base).exists() + + # materialize_json file is unlinked WITHOUT removing the system temp root + f = mcp_runner.materialize_json({"a": 1}) + temp_root = _Path(_tempfile.gettempdir()) + mcp_runner.cleanup_materialized(f) + assert not _Path(f).exists() + assert temp_root.is_dir() # temp root itself untouched diff --git a/tests/unit/test_reporting_coverage.py b/tests/unit/test_reporting_coverage.py index 17c43d7..4a2d6f8 100644 --- a/tests/unit/test_reporting_coverage.py +++ b/tests/unit/test_reporting_coverage.py @@ -94,96 +94,14 @@ def test_build_coverage_rows_full_coverage_and_missing_csv(tmp_path: Path): assert beta["datasets"] == 0 and beta["complexity_size"] == "" # defaulted, no CSV -def test_audited_counts_drive_translation_and_deterministic_coverage(tmp_path: Path) -> None: - metadata = tmp_path / "metadata" - metadata.mkdir() - inventory = { - "source": "airflow", - "pipelines": [ - { - "name": "verified_with_gap", - "activities": [], - "audited_activity_count": 8, - "deterministic_count": 7, - "agentic_count": 1, - "failed_count": 0, - "excluded_count": 0, - "reconciliation_status": "verified_with_gaps", - "migration_status": "included", - "findings": [{"fingerprint": "abc123", "severity": "gap"}], - }, - { - "name": "failed", - "activities": [], - "audited_activity_count": 9, - "deterministic_count": 7, - "agentic_count": 1, - "failed_count": 1, - "excluded_count": 0, - "reconciliation_status": "failed", - "migration_status": "included", - "findings": [{"fingerprint": "def456", "severity": "failed"}], - }, - ], - } - (metadata / "inventory.json").write_text(json.dumps(inventory), encoding="utf-8") - - rows = {row["pipeline"]: row for row in build_coverage_rows(metadata)} - - verified = rows["verified_with_gap"] - assert verified["activities"] == 8 - assert verified["audited_activities"] == 8 - assert verified["coverage_pct"] == 100.0 - assert verified["deterministic_coverage_pct"] == 87.5 - assert verified["code_attached_coverage_pct"] == 87.5 - assert verified["resolved_agentic_count"] == 0 - assert verified["unresolved_agentic_count"] == 1 - assert json.loads(verified["agentic_resolution_outcomes"]) == { - "resolved": 0, - "needs_input": 0, - "deferred": 0, - "declined": 0, - "unreviewed": 1, - } - assert verified["finding_count"] == 1 - assert json.loads(verified["finding_fingerprints"]) == ["abc123"] - - failed = rows["failed"] - assert failed["activities"] == 9 - assert failed["failed_activities"] == 1 - assert failed["coverage_pct"] == 88.9 - assert failed["deterministic_coverage_pct"] == 77.8 - assert failed["code_attached_coverage_pct"] == 77.8 - assert failed["reconciliation_status"] == "failed" - - -def test_excluded_activities_remain_in_coverage_denominator(tmp_path: Path) -> None: - metadata = tmp_path / "metadata" - metadata.mkdir() - inventory = { - "source": "airflow", - "pipelines": [ - { - "name": "excluded", - "activities": [], - "audited_activity_count": 3, - "deterministic_count": 0, - "agentic_count": 0, - "failed_count": 0, - "excluded_count": 3, - "reconciliation_status": "verified", - "migration_status": "excluded", - "findings": [], - } - ], - } - (metadata / "inventory.json").write_text(json.dumps(inventory), encoding="utf-8") - - row = build_coverage_rows(metadata)[0] - - assert row["activities"] == 3 - assert row["excluded_activities"] == 3 - assert row["coverage_pct"] == 0.0 - assert row["deterministic_coverage_pct"] == 0.0 - assert row["code_attached_coverage_pct"] == 0.0 - assert row["migration_status"] == "excluded" +def test_has_insights_column_reflects_insights_key(tmp_path: Path): + md = _write_metadata(tmp_path) # writes inventory.json with no insights key + rows = build_coverage_rows(md) + assert all(row["has_insights"] is False for row in rows) + + inv_path = md / "inventory.json" + inv = json.loads(inv_path.read_text()) + inv["insights"] = {"overview": "x", "pipeline_insights": [], "pipeline_relationships": []} + inv_path.write_text(json.dumps(inv), encoding="utf-8") + rows2 = build_coverage_rows(md) + assert all(row["has_insights"] is True for row in rows2) diff --git a/tests/unit/test_reporting_results.py b/tests/unit/test_reporting_results.py index f7232cc..ac705b7 100644 --- a/tests/unit/test_reporting_results.py +++ b/tests/unit/test_reporting_results.py @@ -238,40 +238,63 @@ def test_write_results_executes_create_schema_check_then_insert(tmp_path: Path): stmts = client.statement_execution.statements assert len(stmts) == 3 assert stmts[0][0] == "wh1" and stmts[0][1].startswith("CREATE TABLE IF NOT EXISTS") - assert stmts[1][1] == "SHOW COLUMNS IN cat.sch.tbl" - assert stmts[2][1].startswith("INSERT INTO cat.sch.tbl") - assert run_id in stmts[2][1] - - -def test_write_results_evolves_an_existing_legacy_schema_before_insert(tmp_path: Path) -> None: - legacy_columns = { - "run_id", - "run_date", - "run_by", - "pipeline", - "activities", - "datasets", - "linked_services", - "collapsible_patterns", - "databricks_native_activities", - "control_flow_activities", - "other_activities", - "deterministic_activities", - "agentic_activities", - "unsupported_activities", - "coverage_pct", - "complexity_score", - "complexity_size", + assert stmts[1][1].startswith("INSERT INTO cat.sch.tbl") + assert run_id in stmts[1][1] + + +def _base_row(pipeline: str = "p1", *, has_insights: bool = False) -> dict: + """Minimal coverage row with all required columns, mirroring COVERAGE_METRIC_COLUMNS.""" + return { + "pipeline": pipeline, + "activities": 1, + "datasets": 0, + "linked_services": 0, + "collapsible_patterns": 0, + "databricks_native_activities": 1, + "control_flow_activities": 0, + "other_activities": 0, + "deterministic_activities": 1, + "agentic_activities": 0, + "unsupported_activities": 0, + "coverage_pct": 100.0, + "complexity_score": 2, + "complexity_size": "S", + "has_insights": has_insights, } - client = _FakeClient([_FakeWarehouse("wh1", "RUNNING", serverless=True)], columns=legacy_columns) - - R.write_results(_metadata(tmp_path), "cat.sch.tbl", client=client) - - statements = [statement for _warehouse, statement in client.statement_execution.statements] - assert len(statements) == 4 - assert statements[2].startswith("ALTER TABLE cat.sch.tbl ADD COLUMNS") - assert "audited_activities INT" in statements[2] - assert "deterministic_coverage_pct DOUBLE" in statements[2] - assert "code_attached_coverage_pct DOUBLE" in statements[2] - assert "agentic_resolution_outcomes STRING" in statements[2] - assert statements[3].startswith("INSERT INTO cat.sch.tbl") + + +def test_insert_sql_renders_has_insights_as_boolean_literal(): + """has_insights must render as TRUE/FALSE, not as integer 1/0. + + Databricks ANSI storeAssignmentPolicy rejects int literals into BOOLEAN DDL columns. + """ + rows = [_base_row("pipe_true", has_insights=True), _base_row("pipe_false", has_insights=False)] + sql = R.build_insert_sql("cat.sch.tbl", rows, "run-x") + + # TRUE/FALSE must appear; integer literals 1 or 0 must NOT stand in for has_insights. + assert "TRUE" in sql, "expected SQL boolean TRUE for has_insights=True" + assert "FALSE" in sql, "expected SQL boolean FALSE for has_insights=False" + + # Confirm neither row falls back to an integer representation: split off the VALUES + # portion and verify the has_insights position for each tuple. + from flowx.reporting.coverage import COVERAGE_METRIC_COLUMNS + + hi_index = list(COVERAGE_METRIC_COLUMNS).index("has_insights") + # Each VALUES tuple follows CURRENT_USER(), so the metric columns start at position 3 + # (run_id, CURRENT_TIMESTAMP(), CURRENT_USER() are the first three). + for line in sql.split("\n"): + line = line.strip().rstrip(",") + if not line.startswith("("): + continue + # Strip outer parens and split on ", " is unreliable for nested strings; + # use a simple positional approach: split by ", " after removing the outer parens. + inner = line[1:-1] if line.endswith(")") else line[1:] + # Find the metric section after the third comma-separated token + # (run_id literal, CURRENT_TIMESTAMP(), CURRENT_USER()) + parts = inner.split(", ", 3) # at most 4 chunks; last chunk is the metrics + if len(parts) < 4: + continue + metric_parts = parts[3].split(", ") + if hi_index < len(metric_parts): + hi_val = metric_parts[hi_index] + assert hi_val in ("TRUE", "FALSE"), f"has_insights rendered as {hi_val!r} instead of TRUE/FALSE" diff --git a/uv.lock b/uv.lock index 0dbdf9d..db721dd 100644 --- a/uv.lock +++ b/uv.lock @@ -6,7 +6,7 @@ requires-python = ">=3.12" name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] @@ -28,7 +28,7 @@ wheels = [ name = "argcomplete" version = "3.6.3" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", upload-time = "2025-10-20T03:33:34.741Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, ] @@ -37,7 +37,7 @@ wheels = [ name = "attrs" version = "26.1.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] @@ -46,7 +46,7 @@ wheels = [ name = "certifi" version = "2026.5.20" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] @@ -112,7 +112,7 @@ wheels = [ name = "charset-normalizer" version = "3.4.7" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", upload-time = "2026-04-02T09:28:39.342Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, { url = "https://pypi-proxy.dev.databricks.com/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, @@ -197,7 +197,7 @@ wheels = [ name = "colorama" version = "0.4.6" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] @@ -206,7 +206,7 @@ wheels = [ name = "coverage" version = "7.13.5" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", upload-time = "2026-03-17T10:33:18.341Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, { url = "https://pypi-proxy.dev.databricks.com/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, @@ -420,7 +420,7 @@ wheels = [ name = "h11" version = "0.16.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] @@ -457,7 +457,7 @@ wheels = [ name = "httpx-sse" version = "0.4.3" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", upload-time = "2025-10-10T21:48:22.271Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] @@ -466,7 +466,7 @@ wheels = [ name = "idna" version = "3.15" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] @@ -475,7 +475,7 @@ wheels = [ name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] @@ -511,7 +511,7 @@ wheels = [ name = "librt" version = "0.8.1" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", upload-time = "2026-02-17T16:13:06.101Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, { url = "https://pypi-proxy.dev.databricks.com/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, @@ -639,7 +639,7 @@ wheels = [ name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] @@ -648,7 +648,7 @@ wheels = [ name = "packaging" version = "26.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] @@ -657,7 +657,7 @@ wheels = [ name = "pathspec" version = "1.0.4" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] @@ -666,7 +666,7 @@ wheels = [ name = "pluggy" version = "1.6.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] @@ -675,7 +675,7 @@ wheels = [ name = "protobuf" version = "6.33.6" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, { url = "https://pypi-proxy.dev.databricks.com/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, @@ -690,7 +690,7 @@ wheels = [ name = "pyasn1" version = "0.6.3" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", upload-time = "2026-03-17T01:06:53.382Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, ] @@ -711,7 +711,7 @@ wheels = [ name = "pycparser" version = "3.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] @@ -824,7 +824,7 @@ wheels = [ name = "pygments" version = "2.20.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] @@ -833,7 +833,7 @@ wheels = [ name = "pyjwt" version = "2.13.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", upload-time = "2026-05-21T19:54:36.618Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, ] @@ -863,7 +863,7 @@ wheels = [ name = "python-dotenv" version = "1.2.2" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] @@ -872,7 +872,7 @@ wheels = [ name = "python-multipart" version = "0.0.32" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] @@ -900,7 +900,7 @@ wheels = [ name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://pypi-proxy.dev.databricks.com/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, @@ -975,7 +975,7 @@ wheels = [ name = "rpds-py" version = "2026.5.1" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", upload-time = "2026-05-28T12:02:13.232Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, { url = "https://pypi-proxy.dev.databricks.com/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, @@ -1085,7 +1085,7 @@ wheels = [ name = "ruff" version = "0.15.8" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", upload-time = "2026-03-26T18:39:38.675Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, { url = "https://pypi-proxy.dev.databricks.com/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, @@ -1110,7 +1110,7 @@ wheels = [ name = "sqlglot" version = "30.8.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/0f/64/89299aefc6ebdf4fc899f5dc14c7fcb7eb9da9290a2b4d615ae7ab884b17/sqlglot-30.8.0.tar.gz", hash = "sha256:1c5f93fb742dd9aaa75eee6bb33a637794a858b9a86375fac23a2dc0f7bc127e", size = 5869750, upload-time = "2026-05-13T09:04:38.923Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/0f/64/89299aefc6ebdf4fc899f5dc14c7fcb7eb9da9290a2b4d615ae7ab884b17/sqlglot-30.8.0.tar.gz", hash = "sha256:1c5f93fb742dd9aaa75eee6bb33a637794a858b9a86375fac23a2dc0f7bc127e", upload-time = "2026-05-13T09:04:38.923Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/88/4e/80705091aaf9c95e125d243f0aa871bc9f3670b4c9d963e6bad3b3dce8ff/sqlglot-30.8.0-py3-none-any.whl", hash = "sha256:af903378c331d5b72277a1b41118f07bc3e50cf4478e2d47eed12c96ee6a22a4", size = 687831, upload-time = "2026-05-13T09:04:36.336Z" }, ] @@ -1145,7 +1145,7 @@ wheels = [ name = "tomlkit" version = "0.15.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", size = 161875, upload-time = "2026-05-10T07:38:22.245Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", upload-time = "2026-05-10T07:38:22.245Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, ] @@ -1154,7 +1154,7 @@ wheels = [ name = "types-pyyaml" version = "6.0.12.20250915" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", upload-time = "2025-09-15T03:01:00.728Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, ] @@ -1163,7 +1163,7 @@ wheels = [ name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] @@ -1184,7 +1184,7 @@ wheels = [ name = "urllib3" version = "2.7.0" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] @@ -1206,7 +1206,7 @@ wheels = [ name = "xmltodict" version = "1.0.4" source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", size = 26124, upload-time = "2026-02-22T02:21:22.074Z" } +sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", upload-time = "2026-02-22T02:21:22.074Z" } wheels = [ { url = "https://pypi-proxy.dev.databricks.com/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, ] From 826a19c952fd797bfd568d81d7349e5e9c5e356c Mon Sep 17 00:00:00 2001 From: matthewmoorcroft <31916486+matthewmoorcroft@users.noreply.github.com> Date: Tue, 1 Sep 2026 13:43:57 +0100 Subject: [PATCH 3/7] Point uv.lock at public PyPI Rewrite internal package-proxy URLs (pypi-proxy.dev.databricks.com) to pypi.org / files.pythonhosted.org so public CI resolves deps. Same pinned versions and hashes; matches main. Co-authored-by: Isaac --- uv.lock | 1416 +++++++++++++++++++++++++++---------------------------- 1 file changed, 708 insertions(+), 708 deletions(-) diff --git a/uv.lock b/uv.lock index db721dd..7f8c3bb 100644 --- a/uv.lock +++ b/uv.lock @@ -5,338 +5,338 @@ requires-python = ">=3.12" [[package]] name = "annotated-types" version = "0.7.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", upload-time = "2024-05-20T21:33:25.928Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] name = "anyio" version = "4.13.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] [[package]] name = "argcomplete" version = "3.6.3" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", upload-time = "2025-10-20T03:33:34.741Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", upload-time = "2025-10-20T03:33:34.741Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, + { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, ] [[package]] name = "attrs" version = "26.1.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", upload-time = "2026-03-19T14:22:25.026Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] name = "certifi" version = "2026.5.20" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", upload-time = "2026-05-20T11:46:50.073Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] [[package]] name = "cffi" version = "2.0.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.7" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", upload-time = "2026-04-02T09:28:39.342Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", upload-time = "2026-04-02T09:28:39.342Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] name = "click" version = "8.4.1" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, ] [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", upload-time = "2022-10-25T02:36:22.414Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.13.5" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", upload-time = "2026-03-17T10:33:18.341Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", upload-time = "2026-03-17T10:33:18.341Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [[package]] name = "cryptography" version = "48.0.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, + { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, + { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, + { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, + { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, ] [[package]] @@ -392,185 +392,185 @@ yq = [{ name = "yq", specifier = "~=3.4.3" }] [[package]] name = "databricks-sdk" version = "0.110.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/07/0f/488d61ece084f70a6d4d0ab8b5e38b0902e0b9029d0b72cde99e3f2c6b4a/databricks_sdk-0.110.0.tar.gz", hash = "sha256:b62d806982b37f8160f700d657c37b3bd586c649eb5c8c4c1216090d888c5820", size = 945261, upload-time = "2026-05-19T09:18:46.23Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/0f/488d61ece084f70a6d4d0ab8b5e38b0902e0b9029d0b72cde99e3f2c6b4a/databricks_sdk-0.110.0.tar.gz", hash = "sha256:b62d806982b37f8160f700d657c37b3bd586c649eb5c8c4c1216090d888c5820", size = 945261, upload-time = "2026-05-19T09:18:46.23Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/9d/23/7c2a827890ab120ac349847ec17ab5a37eb4e3bf8f1d0989fd9eec0c1e6a/databricks_sdk-0.110.0-py3-none-any.whl", hash = "sha256:8a23db05be7a304bea43b4fa78b437051ed0f3755b19594429c649ee4159b546", size = 892096, upload-time = "2026-05-19T09:18:44.313Z" }, + { url = "https://files.pythonhosted.org/packages/9d/23/7c2a827890ab120ac349847ec17ab5a37eb4e3bf8f1d0989fd9eec0c1e6a/databricks_sdk-0.110.0-py3-none-any.whl", hash = "sha256:8a23db05be7a304bea43b4fa78b437051ed0f3755b19594429c649ee4159b546", size = 892096, upload-time = "2026-05-19T09:18:44.313Z" }, ] [[package]] name = "google-auth" version = "2.53.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/c6/ad/ff781329bbbdc0974a098d996e89c9e1f7024262f9e3eec442fbb9ad1ac6/google_auth-2.53.0.tar.gz", hash = "sha256:e7e6aa16f6bee7b2b264830fd04f08087a1d5a836df516251a5d15327b246c9c", size = 335844, upload-time = "2026-05-15T20:53:07.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/ad/ff781329bbbdc0974a098d996e89c9e1f7024262f9e3eec442fbb9ad1ac6/google_auth-2.53.0.tar.gz", hash = "sha256:e7e6aa16f6bee7b2b264830fd04f08087a1d5a836df516251a5d15327b246c9c", size = 335844, upload-time = "2026-05-15T20:53:07.928Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/4a/c9/db44165ba7c581268c6d46017ef63339110378305062830104fc7fa144cb/google_auth-2.53.0-py3-none-any.whl", hash = "sha256:6e7449917c599b35126a99ec268ec6880301f2fea41dce198fe8fd83ff642b68", size = 246071, upload-time = "2026-05-15T20:53:05.609Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c9/db44165ba7c581268c6d46017ef63339110378305062830104fc7fa144cb/google_auth-2.53.0-py3-none-any.whl", hash = "sha256:6e7449917c599b35126a99ec268ec6880301f2fea41dce198fe8fd83ff642b68", size = 246071, upload-time = "2026-05-15T20:53:05.609Z" }, ] [[package]] name = "h11" version = "0.16.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", upload-time = "2025-04-24T03:35:25.427Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "httpcore" version = "1.0.9" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] name = "httpx" version = "0.28.1" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "httpx-sse" version = "0.4.3" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", upload-time = "2025-10-10T21:48:22.271Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", upload-time = "2025-10-10T21:48:22.271Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] [[package]] name = "idna" version = "3.15" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", upload-time = "2026-05-12T22:45:57.011Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] name = "iniconfig" version = "2.3.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", upload-time = "2025-10-18T21:55:43.219Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "jsonschema" version = "4.26.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "jsonschema-specifications" }, { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] [[package]] name = "jsonschema-specifications" version = "2025.9.1" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] [[package]] name = "librt" version = "0.8.1" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", upload-time = "2026-02-17T16:13:06.101Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", upload-time = "2026-02-17T16:13:06.101Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, + { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, + { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, + { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, + { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, ] [[package]] name = "mcp" version = "1.27.2" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "httpx" }, @@ -587,255 +587,255 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/27/3c/347cf965d313f5d41764e7d46bea6ffe7d9ef13b983cc429b0340962a082/mcp-1.27.2.tar.gz", hash = "sha256:8e02db104096d1c25b28e64bde29a5c32b31bc241710213e12fd4d84985bdfef", size = 621116, upload-time = "2026-05-29T17:16:04.039Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/3c/347cf965d313f5d41764e7d46bea6ffe7d9ef13b983cc429b0340962a082/mcp-1.27.2.tar.gz", hash = "sha256:8e02db104096d1c25b28e64bde29a5c32b31bc241710213e12fd4d84985bdfef", size = 621116, upload-time = "2026-05-29T17:16:04.039Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/c9/11/252c6f971dc4f16af1d98a1c469d8ba523aab00d1bb76b4d3bc1ff32eacc/mcp-1.27.2-py3-none-any.whl", hash = "sha256:d6ff5160c6ca65d93013626efb3fc249de683c30b2d8570755ceddd490344de5", size = 220498, upload-time = "2026-05-29T17:16:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/c9/11/252c6f971dc4f16af1d98a1c469d8ba523aab00d1bb76b4d3bc1ff32eacc/mcp-1.27.2-py3-none-any.whl", hash = "sha256:d6ff5160c6ca65d93013626efb3fc249de683c30b2d8570755ceddd490344de5", size = 220498, upload-time = "2026-05-29T17:16:02.442Z" }, ] [[package]] name = "mypy" version = "1.20.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" }, + { url = "https://files.pythonhosted.org/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" }, + { url = "https://files.pythonhosted.org/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" }, + { url = "https://files.pythonhosted.org/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" }, + { url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" }, + { url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" }, + { url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" }, + { url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" }, + { url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" }, + { url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" }, + { url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" }, + { url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" }, + { url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", upload-time = "2025-04-22T14:54:24.164Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "packaging" version = "26.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", upload-time = "2026-01-21T20:50:39.064Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] name = "pathspec" version = "1.0.4" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", upload-time = "2026-01-27T03:59:46.938Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "pluggy" version = "1.6.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", upload-time = "2025-05-15T12:30:07.975Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "protobuf" version = "6.33.6" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", upload-time = "2026-03-18T19:05:00.988Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] [[package]] name = "pyasn1" version = "0.6.3" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", upload-time = "2026-03-17T01:06:53.382Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", upload-time = "2026-03-17T01:06:53.382Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, ] [[package]] name = "pyasn1-modules" version = "0.4.2" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, ] [[package]] name = "pycparser" version = "3.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", upload-time = "2026-01-21T14:26:51.89Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pydantic" version = "2.13.4" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, ] [[package]] name = "pydantic-core" version = "2.46.4" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, ] [[package]] name = "pydantic-settings" version = "2.14.1" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, ] [[package]] name = "pygments" version = "2.20.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", upload-time = "2026-03-29T13:29:33.898Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pyjwt" version = "2.13.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", upload-time = "2026-05-21T19:54:36.618Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", upload-time = "2026-05-21T19:54:36.618Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, ] [package.optional-dependencies] @@ -846,7 +846,7 @@ crypto = [ [[package]] name = "pytest" version = "8.4.2" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, @@ -854,374 +854,374 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] name = "python-dotenv" version = "1.2.2" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", upload-time = "2026-03-01T16:00:26.196Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] [[package]] name = "python-multipart" version = "0.0.32" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", upload-time = "2026-06-04T16:18:58.647Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] [[package]] name = "pywin32" version = "312" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, ] [[package]] name = "pyyaml" version = "6.0.3" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", upload-time = "2025-09-25T21:33:16.546Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] name = "referencing" version = "0.37.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] name = "requests" version = "2.34.2" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] name = "rpds-py" version = "2026.5.1" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", upload-time = "2026-05-28T12:02:13.232Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", upload-time = "2026-05-28T12:02:13.232Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, ] [[package]] name = "ruff" version = "0.15.8" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", upload-time = "2026-03-26T18:39:38.675Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", upload-time = "2026-03-26T18:39:38.675Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, - { url = "https://pypi-proxy.dev.databricks.com/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, ] [[package]] name = "sqlglot" version = "30.8.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/0f/64/89299aefc6ebdf4fc899f5dc14c7fcb7eb9da9290a2b4d615ae7ab884b17/sqlglot-30.8.0.tar.gz", hash = "sha256:1c5f93fb742dd9aaa75eee6bb33a637794a858b9a86375fac23a2dc0f7bc127e", upload-time = "2026-05-13T09:04:38.923Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/64/89299aefc6ebdf4fc899f5dc14c7fcb7eb9da9290a2b4d615ae7ab884b17/sqlglot-30.8.0.tar.gz", hash = "sha256:1c5f93fb742dd9aaa75eee6bb33a637794a858b9a86375fac23a2dc0f7bc127e", upload-time = "2026-05-13T09:04:38.923Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/88/4e/80705091aaf9c95e125d243f0aa871bc9f3670b4c9d963e6bad3b3dce8ff/sqlglot-30.8.0-py3-none-any.whl", hash = "sha256:af903378c331d5b72277a1b41118f07bc3e50cf4478e2d47eed12c96ee6a22a4", size = 687831, upload-time = "2026-05-13T09:04:36.336Z" }, + { url = "https://files.pythonhosted.org/packages/88/4e/80705091aaf9c95e125d243f0aa871bc9f3670b4c9d963e6bad3b3dce8ff/sqlglot-30.8.0-py3-none-any.whl", hash = "sha256:af903378c331d5b72277a1b41118f07bc3e50cf4478e2d47eed12c96ee6a22a4", size = 687831, upload-time = "2026-05-13T09:04:36.336Z" }, ] [[package]] name = "sse-starlette" version = "3.4.4" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/f7/2b/58abc2d1fd397e7dde08e947e05c884d8ef2f78d5e2588c17a12d42d6994/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0", size = 31819, upload-time = "2026-05-12T17:37:17.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/2b/58abc2d1fd397e7dde08e947e05c884d8ef2f78d5e2588c17a12d42d6994/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0", size = 31819, upload-time = "2026-05-12T17:37:17.019Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/dc/67/805710444ea8cc75fbf70b920ed431a560c4bf9c57f7d5a3117213189399/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973", size = 16514, upload-time = "2026-05-12T17:37:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/805710444ea8cc75fbf70b920ed431a560c4bf9c57f7d5a3117213189399/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973", size = 16514, upload-time = "2026-05-12T17:37:15.601Z" }, ] [[package]] name = "starlette" version = "1.2.1" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/25/44/ec35f1b6e83094b997da438a02c8c9b0ade2b1e84cfc48bd4656780760a6/starlette-1.2.1.tar.gz", hash = "sha256:9b9b5ebb992e67d6093741e63c2f59e4f6fff986f81163c087867bd7b924b3f6", size = 2701854, upload-time = "2026-05-31T01:07:51.847Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/44/ec35f1b6e83094b997da438a02c8c9b0ade2b1e84cfc48bd4656780760a6/starlette-1.2.1.tar.gz", hash = "sha256:9b9b5ebb992e67d6093741e63c2f59e4f6fff986f81163c087867bd7b924b3f6", size = 2701854, upload-time = "2026-05-31T01:07:51.847Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/1c/54/196d0c1db10af76baa4f64894448505d60d3cdf70ef92cbb35f46a4e4c71/starlette-1.2.1-py3-none-any.whl", hash = "sha256:4de0082d08c8f6764a85a54cf1120d6939507a19905c7768acad2a9f875d2b89", size = 73350, upload-time = "2026-05-31T01:07:50.09Z" }, + { url = "https://files.pythonhosted.org/packages/1c/54/196d0c1db10af76baa4f64894448505d60d3cdf70ef92cbb35f46a4e4c71/starlette-1.2.1-py3-none-any.whl", hash = "sha256:4de0082d08c8f6764a85a54cf1120d6939507a19905c7768acad2a9f875d2b89", size = 73350, upload-time = "2026-05-31T01:07:50.09Z" }, ] [[package]] name = "tomlkit" version = "0.15.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", upload-time = "2026-05-10T07:38:22.245Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", upload-time = "2026-05-10T07:38:22.245Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, ] [[package]] name = "types-pyyaml" version = "6.0.12.20250915" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", upload-time = "2025-09-15T03:01:00.728Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", upload-time = "2025-09-15T03:01:00.728Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", upload-time = "2025-08-25T13:49:26.313Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "typing-inspection" version = "0.4.2" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]] name = "urllib3" version = "2.7.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", upload-time = "2026-05-07T16:13:18.596Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] name = "uvicorn" version = "0.49.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, ] [[package]] name = "xmltodict" version = "1.0.4" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", upload-time = "2026-02-22T02:21:22.074Z" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", upload-time = "2026-02-22T02:21:22.074Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, + { url = "https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, ] [[package]] name = "yq" version = "3.4.3" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, { name = "pyyaml" }, { name = "tomlkit" }, { name = "xmltodict" }, ] -sdist = { url = "https://pypi-proxy.dev.databricks.com/packages/38/6a/eb9721ed0929d0f55d167c2222d288b529723afbef0a07ed7aa6cca72380/yq-3.4.3.tar.gz", hash = "sha256:ba586a1a6f30cf705b2f92206712df2281cd320280210e7b7b80adcb8f256e3b", size = 33214, upload-time = "2024-04-27T15:39:43.29Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/6a/eb9721ed0929d0f55d167c2222d288b529723afbef0a07ed7aa6cca72380/yq-3.4.3.tar.gz", hash = "sha256:ba586a1a6f30cf705b2f92206712df2281cd320280210e7b7b80adcb8f256e3b", size = 33214, upload-time = "2024-04-27T15:39:43.29Z" } wheels = [ - { url = "https://pypi-proxy.dev.databricks.com/packages/f2/ba/d1b21f3e57469030bd6536b91bb28fedd2511d4e68b5a575f2bdb3a3dbb6/yq-3.4.3-py3-none-any.whl", hash = "sha256:547e34bc3caacce83665fd3429bf7c85f8e8b6b9aaee3f953db1ad716ff3434d", size = 18812, upload-time = "2024-04-27T15:39:41.652Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ba/d1b21f3e57469030bd6536b91bb28fedd2511d4e68b5a575f2bdb3a3dbb6/yq-3.4.3-py3-none-any.whl", hash = "sha256:547e34bc3caacce83665fd3429bf7c85f8e8b6b9aaee3f953db1ad716ff3434d", size = 18812, upload-time = "2024-04-27T15:39:41.652Z" }, ] From 978098129f6665ed069bb871cb3f5f32a9b92bf1 Mon Sep 17 00:00:00 2001 From: Matthew Moorcroft Date: Wed, 9 Sep 2026 17:06:32 +0100 Subject: [PATCH 4/7] Make agentic insights source-agnostic (ADF + Airflow) Airflow is now a first-class deterministic source, so the insights layer should apply to both. The engine (parser/pipeline_insights.py) and the enrich command were already source-neutral (they operate on the inventory dict); this closes the two remaining ADF couplings: - Move the insight models (Insights, PipelineInsight, PipelineRelationship, LineageEdgeRef, RecommendedPattern, SystemRecommendation) out of the ADF AST module into a source-neutral models/insights.py -- they are used only by the tests, the runtime path is dict-based. Update the test import. - flowx-discover SKILL.md Step 5: keep the neutral authoring core (schema, sparse/ranked philosophy, edge-accountability model, Databricks target vocabulary) shared, and branch the two genuinely source-specific pieces by --source: the source deep-dive (ADF ARM *.arm.json vs Airflow DAG source) and the pattern vocabulary (ADF constructs table + new Airflow operators table). Neutralise incidental ADF wording in the edge-authoring rules. Out of scope: emitting deterministic lineage from sources/airflow (tracked separately). Until then an Airflow inventory has no lineage block, so only per-pipeline insights and inferred edges validate there -- control/data annotation edges degrade gracefully, no change needed here. Full unit suite: 1306 passed with the mcp extra; 1224 passed / 3 skipped without it. make fmt + mypy clean. Co-authored-by: Isaac --- skills/flowx-discover/SKILL.md | 58 +++++++--- src/flowx/models/adf_ast.py | 154 ------------------------- src/flowx/models/insights.py | 162 +++++++++++++++++++++++++++ tests/unit/test_pipeline_insights.py | 2 +- 4 files changed, 205 insertions(+), 171 deletions(-) create mode 100644 src/flowx/models/insights.py diff --git a/skills/flowx-discover/SKILL.md b/skills/flowx-discover/SKILL.md index 1d4a201..586bd46 100644 --- a/skills/flowx-discover/SKILL.md +++ b/skills/flowx-discover/SKILL.md @@ -238,16 +238,24 @@ system*. Author that judgment now and merge it into `inventory.json` under an 1. **Read** the just-written `inventory.json` (`pipelines`, `lineage`, `summary`) and `profile_report.csv`. **Then, before authoring, deep-dive the source.** The inventory is a deterministic skeleton (types, strategy, control edges); the - *why* and *how* — queries, Switch conditions, notebook paths, dataset - parameters — live only in the verbatim ARM. The `metadata/` folder holds one - `*.arm.json` file per pipeline; each file is a **flat single-pipeline object** - shaped `{"name": "", "properties": {"activities": [...], ...}}` (no - `resources[]` array, no top-level `type`). To inspect a pipeline, **glob - `metadata/*.arm.json` and match on each file's top-level `"name"` field** — do - **not** construct a filename from the pipeline name (names are slugified and - lossy, so a built path can miss or collide). The activities are under - `properties.activities` (recurse into nested `ForEach`/`If`/`Switch` bodies). - Read the ARM for any pipeline you write an insight or relationship about. + *why* and *how* — queries, branch conditions, notebook paths, parameters — live + only in the verbatim source artifacts. **Which artifact depends on `--source`:** + - **ADF** — the `metadata/` folder holds one `*.arm.json` file per pipeline; + each is a **flat single-pipeline object** shaped `{"name": "", + "properties": {"activities": [...], ...}}` (no `resources[]` array, no + top-level `type`). To inspect a pipeline, **glob `metadata/*.arm.json` and + match on each file's top-level `"name"` field** — do **not** construct a + filename from the pipeline name (names are slugified and lossy, so a built + path can miss or collide). Activities are under `properties.activities` + (recurse into nested `ForEach`/`If`/`Switch` bodies). + - **Airflow** — the inventory is built from the parsed DAGs; the *why* and *how* + live in the **DAG source** (the `.py` files under the `--source-path` you + discovered from) — task callables, operator arguments, templated params, and + `set_upstream` / `>>` dependencies. Read the DAG module for any pipeline you + write about; recurse into `TaskGroup`s and dynamically mapped (`.expand`) + tasks. + + Read the source for any pipeline you write an insight or relationship about. 2. **Author** an `insights` object: - `overview` — the whole factory as one system, plus the single biggest migration steer. @@ -318,10 +326,13 @@ system*. Author that judgment now and merge it into `inventory.json` under an **verify** a connector's status in the docs/release notes (e.g. the Lakeflow Connect SQL Server connector) rather than assuming GA. - **Recognized-pattern vocabulary — a reference menu, NOT an allowlist.** Common - ADF→Databricks target patterns with **current** product names. Use it to stay - grounded and consistent, but reach past it whenever the holistic view calls for a - better or newer fit: + **Recognized-pattern vocabulary — a reference menu, NOT an allowlist.** Target + patterns with **current** product names; use it to stay grounded and consistent, + but reach past it whenever the holistic view calls for a better or newer fit. The + **target (right side) is source-neutral Databricks**; the **left side is keyed by + `--source`** — use the table matching the source you discovered from. + + *ADF constructs → Databricks:* | Pipeline does… | Simplifying target — `simplification_pattern: true` (rank first) | Fallback — `false` | |---|---|---| @@ -338,6 +349,20 @@ system*. Author that judgment now and merge it into `inventory.json` under an | Run-state / control tables | Lakeflow job & task run state + `dbutils.jobs.taskValues` | — | | Clone family (many near-identical pipelines) | one **parameterized Lakeflow Job** invoked N times | — | + *Airflow operators → Databricks:* + + | DAG uses… | Simplifying target — `simplification_pattern: true` (rank first) | Fallback — `false` | + |---|---|---| + | DB extract via `MsSqlOperator` / `JdbcOperator` / custom hook | **Lakeflow Connect** managed connector (change-tracking/CDC → Delta) | JDBC read + `MERGE INTO` | + | Incremental load w/ XCom or Variable watermark | **Lakeflow Declarative Pipelines `AUTO CDC`** | Delta `MERGE INTO` + `dbutils.jobs.taskValues` | + | File sensor + load (`*FileSensor` → transform) | **Auto Loader** (`cloudFiles`, file-notification mode) | — | + | `SparkSubmitOperator` / `DatabricksSubmitRunOperator` | native **Lakeflow Job** task (notebook / JAR / Python) | — | + | `PythonOperator` glue / bespoke script | notebook or Python task in a **Lakeflow Job** | — | + | `TriggerDagRunOperator` / `ExternalTaskSensor` fan-out | **Lakeflow Jobs** run-job task + job parameters | — | + | Dynamic task mapping (`.expand`) over a list | **Lakeflow Jobs** for-each task | — | + | `BashOperator` shelling out to a script | native task (notebook / Python) driven by job parameters | — | + | Custom logging / observability via XComs or a side table | **system tables (`system.lakeflow.*`) + native job notifications + AI/BI dashboard** | — | + **Emit current names, not legacy ones:** Lakeflow Jobs (was Databricks Workflows), Lakeflow Declarative Pipelines (was Delta Live Tables/DLT), `AUTO CDC` (was `APPLY CHANGES INTO`), Declarative Automation Bundles (was Databricks Asset @@ -407,7 +432,8 @@ system*. Author that judgment now and merge it into `inventory.json` under an deterministic phase did **not** record as an edge, by any mechanism. Set `edge_identity` to a short descriptor of what couples the two pipelines (e.g. the shared table/asset, or the nature of the dependency), and **you - must** supply `evidence` (the concrete ARM observation behind it) and + must** supply `evidence` (the concrete source observation behind it — the ARM + activity for ADF, the DAG code for Airflow) and `confidence` (`high` / `medium` / `low`). Report only couplings you can actually evidence; do not invent them. @@ -421,7 +447,7 @@ system*. Author that judgment now and merge it into `inventory.json` under an **Inferred covers several sub-cases — do not restrict it to any one:** - *Data-in-code:* one pipeline's notebook writes a table another's notebook - reads (no ADF dataset, so `data_edges` never saw it). + reads (no declared dataset, so `data_edges` never saw it). - *Ordering dependency:* a producer→consumer hand-off expressed only as sibling `dependsOn` inside a parent orchestrator, which the deterministic phase did not emit as a cross-pipeline edge. diff --git a/src/flowx/models/adf_ast.py b/src/flowx/models/adf_ast.py index 05ab369..86b4feb 100644 --- a/src/flowx/models/adf_ast.py +++ b/src/flowx/models/adf_ast.py @@ -400,157 +400,3 @@ class Lineage: control_edges: list[ControlEdge] = field(default_factory=list) data_edges: list[DataEdge] = field(default_factory=list) - - -# --------------------------------------------------------------------------- -# Agentic insights (discover phase) -- agent-authored judgment merged into -# inventory.json. References pipelines by name. Its cross-pipeline edges are -# either ANNOTATIONS of a deterministic Lineage edge (control/data -- carry no -# facts of their own) or an agent-INFERRED coupling the deterministic layer -# could not see (e.g. data flow that happens inside notebook code, an external -# trigger, a message queue). Inferred edges must cite their evidence and a -# confidence level so they are never mistaken for proven lineage. -# --------------------------------------------------------------------------- - - -@dataclass(slots=True, kw_only=True) -class LineageEdgeRef: - """A typed reference from a PipelineRelationship to one cross-pipeline edge. - - Two tiers: - - * ``"control"`` / ``"data"`` -- an **annotation** of a deterministic edge. - ``edge_identity`` echoes that edge verbatim (``ControlEdge.activity_name`` - for control, ``DataEdge.match_key`` for data) so enrichment can resolve it - against the inventory's ``lineage``. ``evidence`` / ``confidence`` are not - used (the deterministic edge *is* the evidence, confidence is implicitly - high) and must be omitted. - * ``"inferred"`` -- an agent-asserted coupling the deterministic layer did - not find. There is no lineage edge to resolve against, so ``edge_identity`` - is an agent-authored descriptor of what couples the pipelines (e.g. a - shared table or asset name), and ``evidence`` (why the agent believes the - coupling exists) plus ``confidence`` are **required**. This tier stays - pattern-agnostic: it does not encode *why* the deterministic layer missed - the edge, so it generalises to couplings flowx cannot yet see. - - Attributes: - edge_type: The tier -- ``"control"``, ``"data"``, or ``"inferred"``. - edge_identity: For ``"control"`` the ``ControlEdge.activity_name``; for - ``"data"`` the ``DataEdge.match_key`` (both echoed verbatim from a - real edge); for ``"inferred"`` an agent-authored descriptor of the - coupling. - evidence: Inferred edges only -- the observable basis for the asserted - coupling. Required for ``"inferred"``; must be omitted otherwise. - confidence: Inferred edges only -- ``"high"`` / ``"medium"`` / ``"low"``. - Required for ``"inferred"``; must be omitted otherwise. - """ - - edge_type: Literal["control", "data", "inferred"] - edge_identity: str - evidence: str | None = None - confidence: Literal["high", "medium", "low"] | None = None - - -@dataclass(slots=True, kw_only=True) -class RecommendedPattern: - """One ranked Databricks target pattern recommended for a pipeline. - - A pipeline insight carries 1-4 of these, ordered best-first, drawn from the - agent's *holistic* read of the pipeline and grounded in publicly-documented - Databricks capabilities. ``simplification_pattern`` ranks the distinctive - capabilities that collapse a legacy pattern ahead of like-for-like ports and - plain building blocks. - - Attributes: - pattern: The named, publicly-documented Databricks capability (e.g. - ``"Lakeflow Connect SQL Server connector"``). Never an invented name. - fit: One line on why it fits this pipeline / what custom logic it replaces. - simplification_pattern: ``True`` *only* when the pattern uses a **distinctive** - Databricks capability that collapses or eliminates a whole legacy - pattern -- a managed connector (Lakeflow Connect), declarative CDC - (``AUTO CDC``), Auto Loader, or system tables replacing a home-grown - logging tier. ``False`` for a like-for-like port AND for plain native - building blocks that merely re-home the same work (a bare parameterized - Lakeflow Job, a for-each/run-job orchestrator, a plain Delta control - table, ``MERGE INTO``) -- "runs on Databricks" is not a simplification, - so reserve this flag for the capability that makes the old pattern - *disappear*. Rank the ``True`` patterns first. - """ - - pattern: str - fit: str - simplification_pattern: bool - - -@dataclass(slots=True, kw_only=True) -class SystemRecommendation: - """The single top-level architectural decision spanning the whole factory. - - Per-pipeline ``recommended_patterns`` are chosen *under* this decision: the - system-level branch you pick (e.g. adopt a managed connector for an entire - extraction family) cascades into what each pipeline becomes, so it is authored - first and the per-pipeline patterns are kept consistent with it. It captures - the payoff a reader cannot see from any single pipeline card. - - Attributes: - headline: One line naming the decision a migrator must make before any - per-pipeline work (e.g. "Managed ingestion collapses the extraction - factory"). - recommended_patterns: 1-4 whole-system target architectures, ordered - best-first (the simplifying/native branch first), each a - :class:`RecommendedPattern`. ``recommended_patterns[0]`` is the - recommended branch; later entries are the ranked fallbacks. - cascade: What choosing ``recommended_patterns[0]`` collapses or eliminates - across the whole system (e.g. "5 child extractors -> managed connector - pipelines", "version-watermark CSV -> gone"). Empty when the decision - does not cascade. - decision_driver: The gating question that selects the branch (e.g. "Is the - Lakeflow Connect SQL Server connector GA/approved for this source?"); - omit when there is no single deciding factor. - """ - - headline: str - recommended_patterns: list[RecommendedPattern] = field(default_factory=list) - cascade: list[str] = field(default_factory=list) - decision_driver: str | None = None - - -@dataclass(slots=True, kw_only=True) -class PipelineInsight: - """Per-pipeline judgment; references a pipeline by name (foreign key).""" - - pipeline: str - pattern_name: str | None = None - intent: str | None = None - databricks_pattern: str | None = None - recommended_patterns: list[RecommendedPattern] = field(default_factory=list) - conversion_notes: list[str] = field(default_factory=list) - risk_if_ignored: str | None = None - - -@dataclass(slots=True, kw_only=True) -class PipelineRelationship: - """Cross-pipeline judgment. - - Either annotates one deterministic lineage edge (``lineage_edge.edge_type`` - is ``"control"`` / ``"data"``) or records an agent-inferred coupling the - deterministic layer could not see (``"inferred"``). Both endpoints are always - real pipeline names validated against the inventory. - """ - - from_pipeline: str - to_pipeline: str - lineage_edge: LineageEdgeRef - relationship_summary: str | None = None - databricks_pattern: str | None = None - risk_if_ignored: str | None = None - - -@dataclass(slots=True, kw_only=True) -class Insights: - """Agent-authored insights merged into inventory.json under the ``insights`` key.""" - - overview: str | None = None - system_recommendation: SystemRecommendation | None = None - pipeline_insights: list[PipelineInsight] = field(default_factory=list) - pipeline_relationships: list[PipelineRelationship] = field(default_factory=list) diff --git a/src/flowx/models/insights.py b/src/flowx/models/insights.py new file mode 100644 index 0000000..d7ce953 --- /dev/null +++ b/src/flowx/models/insights.py @@ -0,0 +1,162 @@ +"""Agentic insights (discover phase) -- agent-authored judgment merged into inventory.json. + +References pipelines by name. Its cross-pipeline edges are either ANNOTATIONS of a +deterministic ``Lineage`` edge (control/data -- carry no facts of their own) or an +agent-INFERRED coupling the deterministic layer could not see (e.g. data flow that +happens inside notebook code, an external trigger, a message queue). Inferred edges +must cite their evidence and a confidence level so they are never mistaken for proven +lineage. + +These models are **source-neutral**: they describe the shape of the ``insights`` object +the agent authors, independent of whether the pipelines were discovered from ADF or +Airflow. The validate/merge engine in ``flowx.parser.pipeline_insights`` works on the +raw dict form; these dataclasses document the contract and back the unit tests. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Literal + + +@dataclass(slots=True, kw_only=True) +class LineageEdgeRef: + """A typed reference from a PipelineRelationship to one cross-pipeline edge. + + Two tiers: + + * ``"control"`` / ``"data"`` -- an **annotation** of a deterministic edge. + ``edge_identity`` echoes that edge verbatim (``ControlEdge.activity_name`` + for control, ``DataEdge.match_key`` for data) so enrichment can resolve it + against the inventory's ``lineage``. ``evidence`` / ``confidence`` are not + used (the deterministic edge *is* the evidence, confidence is implicitly + high) and must be omitted. + * ``"inferred"`` -- an agent-asserted coupling the deterministic layer did + not find. There is no lineage edge to resolve against, so ``edge_identity`` + is an agent-authored descriptor of what couples the pipelines (e.g. a + shared table or asset name), and ``evidence`` (why the agent believes the + coupling exists) plus ``confidence`` are **required**. This tier stays + pattern-agnostic: it does not encode *why* the deterministic layer missed + the edge, so it generalises to couplings flowx cannot yet see. + + Attributes: + edge_type: The tier -- ``"control"``, ``"data"``, or ``"inferred"``. + edge_identity: For ``"control"`` the ``ControlEdge.activity_name``; for + ``"data"`` the ``DataEdge.match_key`` (both echoed verbatim from a + real edge); for ``"inferred"`` an agent-authored descriptor of the + coupling. + evidence: Inferred edges only -- the observable basis for the asserted + coupling. Required for ``"inferred"``; must be omitted otherwise. + confidence: Inferred edges only -- ``"high"`` / ``"medium"`` / ``"low"``. + Required for ``"inferred"``; must be omitted otherwise. + """ + + edge_type: Literal["control", "data", "inferred"] + edge_identity: str + evidence: str | None = None + confidence: Literal["high", "medium", "low"] | None = None + + +@dataclass(slots=True, kw_only=True) +class RecommendedPattern: + """One ranked Databricks target pattern recommended for a pipeline. + + A pipeline insight carries 1-4 of these, ordered best-first, drawn from the + agent's *holistic* read of the pipeline and grounded in publicly-documented + Databricks capabilities. ``simplification_pattern`` ranks the distinctive + capabilities that collapse a legacy pattern ahead of like-for-like ports and + plain building blocks. + + Attributes: + pattern: The named, publicly-documented Databricks capability (e.g. + ``"Lakeflow Connect SQL Server connector"``). Never an invented name. + fit: One line on why it fits this pipeline / what custom logic it replaces. + simplification_pattern: ``True`` *only* when the pattern uses a **distinctive** + Databricks capability that collapses or eliminates a whole legacy + pattern -- a managed connector (Lakeflow Connect), declarative CDC + (``AUTO CDC``), Auto Loader, or system tables replacing a home-grown + logging tier. ``False`` for a like-for-like port AND for plain native + building blocks that merely re-home the same work (a bare parameterized + Lakeflow Job, a for-each/run-job orchestrator, a plain Delta control + table, ``MERGE INTO``) -- "runs on Databricks" is not a simplification, + so reserve this flag for the capability that makes the old pattern + *disappear*. Rank the ``True`` patterns first. + """ + + pattern: str + fit: str + simplification_pattern: bool + + +@dataclass(slots=True, kw_only=True) +class SystemRecommendation: + """The single top-level architectural decision spanning the whole factory. + + Per-pipeline ``recommended_patterns`` are chosen *under* this decision: the + system-level branch you pick (e.g. adopt a managed connector for an entire + extraction family) cascades into what each pipeline becomes, so it is authored + first and the per-pipeline patterns are kept consistent with it. It captures + the payoff a reader cannot see from any single pipeline card. + + Attributes: + headline: One line naming the decision a migrator must make before any + per-pipeline work (e.g. "Managed ingestion collapses the extraction + factory"). + recommended_patterns: 1-4 whole-system target architectures, ordered + best-first (the simplifying/native branch first), each a + :class:`RecommendedPattern`. ``recommended_patterns[0]`` is the + recommended branch; later entries are the ranked fallbacks. + cascade: What choosing ``recommended_patterns[0]`` collapses or eliminates + across the whole system (e.g. "5 child extractors -> managed connector + pipelines", "version-watermark CSV -> gone"). Empty when the decision + does not cascade. + decision_driver: The gating question that selects the branch (e.g. "Is the + Lakeflow Connect SQL Server connector GA/approved for this source?"); + omit when there is no single deciding factor. + """ + + headline: str + recommended_patterns: list[RecommendedPattern] = field(default_factory=list) + cascade: list[str] = field(default_factory=list) + decision_driver: str | None = None + + +@dataclass(slots=True, kw_only=True) +class PipelineInsight: + """Per-pipeline judgment; references a pipeline by name (foreign key).""" + + pipeline: str + pattern_name: str | None = None + intent: str | None = None + databricks_pattern: str | None = None + recommended_patterns: list[RecommendedPattern] = field(default_factory=list) + conversion_notes: list[str] = field(default_factory=list) + risk_if_ignored: str | None = None + + +@dataclass(slots=True, kw_only=True) +class PipelineRelationship: + """Cross-pipeline judgment. + + Either annotates one deterministic lineage edge (``lineage_edge.edge_type`` + is ``"control"`` / ``"data"``) or records an agent-inferred coupling the + deterministic layer could not see (``"inferred"``). Both endpoints are always + real pipeline names validated against the inventory. + """ + + from_pipeline: str + to_pipeline: str + lineage_edge: LineageEdgeRef + relationship_summary: str | None = None + databricks_pattern: str | None = None + risk_if_ignored: str | None = None + + +@dataclass(slots=True, kw_only=True) +class Insights: + """Agent-authored insights merged into inventory.json under the ``insights`` key.""" + + overview: str | None = None + system_recommendation: SystemRecommendation | None = None + pipeline_insights: list[PipelineInsight] = field(default_factory=list) + pipeline_relationships: list[PipelineRelationship] = field(default_factory=list) diff --git a/tests/unit/test_pipeline_insights.py b/tests/unit/test_pipeline_insights.py index 417feb6..c9c9ba8 100644 --- a/tests/unit/test_pipeline_insights.py +++ b/tests/unit/test_pipeline_insights.py @@ -12,7 +12,7 @@ from flowx.adapter.__main__ import main as adapter_cli_main # noqa: E402 from flowx.mcp import runner as mcp_runner # noqa: E402 from flowx.mcp.server import _cmd_enrich # noqa: E402 -from flowx.models.adf_ast import ( # noqa: E402 +from flowx.models.insights import ( # noqa: E402 Insights, LineageEdgeRef, PipelineInsight, From f71651bf80a99853bc3d0c893b801c4aaa209f82 Mon Sep 17 00:00:00 2001 From: Matthew Moorcroft Date: Thu, 10 Sep 2026 11:11:52 +0100 Subject: [PATCH 5/7] Move per-source insight guidance into the discover source guides Follow the discover skill's own hub-and-spoke convention (SKILL.md: "the shared mechanics live here"; Step 2: "read the matching sources/.md and follow it"). The insights authoring step had inlined ADF/Airflow specifics into the shared SKILL.md. - SKILL.md Step 5 keeps the source-neutral core (schema, analysis method, pattern framework, edge model, enrich) and now points to the source guide for the source deep-dive and the construct->Databricks pattern vocabulary. Marked the step explicitly source-neutral ("runs for every source"). - sources/adf.md and sources/airflow.md each gain an "Insights -- deep-dive & pattern vocabulary" section (ARM *.arm.json / DAG-source deep-dive + the source-construct pattern table) and a pointer back to the shared authoring step. This also wires insights for Airflow: an Airflow run follows sources/airflow.md (per Step 2 routing), which now carries the source deep-dive + pattern table and routes to the shared authoring+enrich step. Previously that step lived only inside SKILL.md's ADF-specific ## Workflow, so an Airflow run never reached it. Docs only; unit suite unchanged (1224 passed / 3 skipped without the mcp extra). Co-authored-by: Isaac --- skills/flowx-discover/SKILL.md | 77 ++++++------------------ skills/flowx-discover/sources/adf.md | 44 ++++++++++++++ skills/flowx-discover/sources/airflow.md | 41 +++++++++++++ 3 files changed, 104 insertions(+), 58 deletions(-) diff --git a/skills/flowx-discover/SKILL.md b/skills/flowx-discover/SKILL.md index 586bd46..6191143 100644 --- a/skills/flowx-discover/SKILL.md +++ b/skills/flowx-discover/SKILL.md @@ -235,27 +235,19 @@ record *what the factory is trying to do* or *how the pipelines relate as a system*. Author that judgment now and merge it into `inventory.json` under an `insights` key. This always runs. +**This step is source-neutral — it runs for every source.** The insight *schema*, the +*analysis method*, and the *pattern framework* below are shared; the source-specific inputs +(how to deep-dive the source, and its construct→Databricks pattern vocabulary) come from the +"Insights — deep-dive & pattern vocabulary" section of your `sources/.md`. + 1. **Read** the just-written `inventory.json` (`pipelines`, `lineage`, `summary`) - and `profile_report.csv`. **Then, before authoring, deep-dive the source.** - The inventory is a deterministic skeleton (types, strategy, control edges); the - *why* and *how* — queries, branch conditions, notebook paths, parameters — live - only in the verbatim source artifacts. **Which artifact depends on `--source`:** - - **ADF** — the `metadata/` folder holds one `*.arm.json` file per pipeline; - each is a **flat single-pipeline object** shaped `{"name": "", - "properties": {"activities": [...], ...}}` (no `resources[]` array, no - top-level `type`). To inspect a pipeline, **glob `metadata/*.arm.json` and - match on each file's top-level `"name"` field** — do **not** construct a - filename from the pipeline name (names are slugified and lossy, so a built - path can miss or collide). Activities are under `properties.activities` - (recurse into nested `ForEach`/`If`/`Switch` bodies). - - **Airflow** — the inventory is built from the parsed DAGs; the *why* and *how* - live in the **DAG source** (the `.py` files under the `--source-path` you - discovered from) — task callables, operator arguments, templated params, and - `set_upstream` / `>>` dependencies. Read the DAG module for any pipeline you - write about; recurse into `TaskGroup`s and dynamically mapped (`.expand`) - tasks. - - Read the source for any pipeline you write an insight or relationship about. + and `profile_report.csv`. **Then, before authoring, deep-dive the source.** The + inventory is a deterministic skeleton (types, strategy, control edges); the *why* + and *how* — queries, branch conditions, notebook paths, parameters — live only in + the verbatim source artifacts. **Which artifact to read, and how, is + source-specific: follow the "Insights — deep-dive & pattern vocabulary" section of + the `sources/.md` guide you used in Step 2.** Read the source for any + pipeline you write an insight or relationship about. 2. **Author** an `insights` object: - `overview` — the whole factory as one system, plus the single biggest migration steer. @@ -326,42 +318,12 @@ system*. Author that judgment now and merge it into `inventory.json` under an **verify** a connector's status in the docs/release notes (e.g. the Lakeflow Connect SQL Server connector) rather than assuming GA. - **Recognized-pattern vocabulary — a reference menu, NOT an allowlist.** Target - patterns with **current** product names; use it to stay grounded and consistent, - but reach past it whenever the holistic view calls for a better or newer fit. The - **target (right side) is source-neutral Databricks**; the **left side is keyed by - `--source`** — use the table matching the source you discovered from. - - *ADF constructs → Databricks:* - - | Pipeline does… | Simplifying target — `simplification_pattern: true` (rank first) | Fallback — `false` | - |---|---|---| - | Extract/Copy from a database (SQL Server, …) | **Lakeflow Connect** managed connector (change-tracking/CDC → Delta) | Auto Loader / JDBC read + `MERGE INTO` | - | Incremental load via watermark | **Lakeflow Declarative Pipelines `AUTO CDC`** | Delta `MERGE INTO` + control table / `dbutils.jobs.taskValues` | - | CDC / SQL Server change tracking | **Lakeflow Connect** or **`AUTO CDC`** | Structured Streaming over the change feed | - | Land + process files | **Auto Loader** (`cloudFiles`, file-notification mode) | — | - | Metadata-driven bulk copy (Lookup→ForEach→Copy) | **Lakeflow Connect** (multi-table) or a parameterized **Lakeflow Jobs** for-each task | — | - | Parent/child `ExecutePipeline` fan-out | **Lakeflow Jobs** for-each task + run-job task + job parameters | — | - | SCD Type 2 (data flow) | **Lakeflow Declarative Pipelines `AUTO CDC`** (SCD Type 2) | — | - | Staged load + stored-proc transform | Spark write to **Delta** + post-load step | — | - | REST API pagination | Python ingestion notebook (requests-based) | Lakeflow Connect SaaS connector if one fits | - | Custom logging / observability tier | **system tables (`system.lakeflow.*`) + native job notifications + AI/BI dashboard** | — | - | Run-state / control tables | Lakeflow job & task run state + `dbutils.jobs.taskValues` | — | - | Clone family (many near-identical pipelines) | one **parameterized Lakeflow Job** invoked N times | — | - - *Airflow operators → Databricks:* - - | DAG uses… | Simplifying target — `simplification_pattern: true` (rank first) | Fallback — `false` | - |---|---|---| - | DB extract via `MsSqlOperator` / `JdbcOperator` / custom hook | **Lakeflow Connect** managed connector (change-tracking/CDC → Delta) | JDBC read + `MERGE INTO` | - | Incremental load w/ XCom or Variable watermark | **Lakeflow Declarative Pipelines `AUTO CDC`** | Delta `MERGE INTO` + `dbutils.jobs.taskValues` | - | File sensor + load (`*FileSensor` → transform) | **Auto Loader** (`cloudFiles`, file-notification mode) | — | - | `SparkSubmitOperator` / `DatabricksSubmitRunOperator` | native **Lakeflow Job** task (notebook / JAR / Python) | — | - | `PythonOperator` glue / bespoke script | notebook or Python task in a **Lakeflow Job** | — | - | `TriggerDagRunOperator` / `ExternalTaskSensor` fan-out | **Lakeflow Jobs** run-job task + job parameters | — | - | Dynamic task mapping (`.expand`) over a list | **Lakeflow Jobs** for-each task | — | - | `BashOperator` shelling out to a script | native task (notebook / Python) driven by job parameters | — | - | Custom logging / observability via XComs or a side table | **system tables (`system.lakeflow.*`) + native job notifications + AI/BI dashboard** | — | + **Recognized-pattern vocabulary — a reference menu, NOT an allowlist.** Each + source guide carries a **source-construct → Databricks** mapping table (with + **current** product names) in its "Insights — deep-dive & pattern vocabulary" + section — use the one in the `sources/.md` you followed. The Databricks + (target) side is source-neutral; use it to stay grounded and consistent, but reach + past it whenever the holistic view calls for a better or newer fit. **Emit current names, not legacy ones:** Lakeflow Jobs (was Databricks Workflows), Lakeflow Declarative Pipelines (was Delta Live Tables/DLT), `AUTO CDC` @@ -432,8 +394,7 @@ system*. Author that judgment now and merge it into `inventory.json` under an deterministic phase did **not** record as an edge, by any mechanism. Set `edge_identity` to a short descriptor of what couples the two pipelines (e.g. the shared table/asset, or the nature of the dependency), and **you - must** supply `evidence` (the concrete source observation behind it — the ARM - activity for ADF, the DAG code for Airflow) and + must** supply `evidence` (the concrete source observation behind it) and `confidence` (`high` / `medium` / `low`). Report only couplings you can actually evidence; do not invent them. diff --git a/skills/flowx-discover/sources/adf.md b/skills/flowx-discover/sources/adf.md index 9997e27..9a59889 100644 --- a/skills/flowx-discover/sources/adf.md +++ b/skills/flowx-discover/sources/adf.md @@ -98,3 +98,47 @@ to a PySpark notebook. Tell the user where the metadata files were written (`/metadata/`), summarise the complexity sizes, and confirm they can proceed to `flowx-convert` with the same ``. + +## Insights — deep-dive & pattern vocabulary + +Reference for the shared agentic-insights step (parent `SKILL.md` Step 5, "Author and merge agentic +insights"). Do this deep-dive before authoring insights for any ADF pipeline. + +**Deep-dive the ARM.** The inventory is a deterministic skeleton (types, strategy, control edges); +the *why* and *how* — queries, Switch conditions, notebook paths, dataset parameters — live only in +the verbatim ARM. The `metadata/` folder holds one `*.arm.json` file per pipeline; each is a **flat +single-pipeline object** shaped `{"name": "", "properties": {"activities": [...], ...}}` +(no `resources[]` array, no top-level `type`). To inspect a pipeline, **glob `metadata/*.arm.json` +and match on each file's top-level `"name"` field** — do **not** construct a filename from the +pipeline name (names are slugified and lossy, so a built path can miss or collide). Activities are +under `properties.activities` (recurse into nested `ForEach`/`If`/`Switch` bodies). Read the ARM for +any pipeline you write an insight or relationship about. + +**ADF constructs → Databricks** — a reference menu, NOT an allowlist; the target side uses current +product names, so reach past it whenever a better or newer fit exists. Flag `simplification_pattern: +true` only on entries that use a distinctive capability, never on the plain-orchestration fallback. + +| Pipeline does… | Simplifying target — `simplification_pattern: true` (rank first) | Fallback — `false` | +|---|---|---| +| Extract/Copy from a database (SQL Server, …) | **Lakeflow Connect** managed connector (change-tracking/CDC → Delta) | Auto Loader / JDBC read + `MERGE INTO` | +| Incremental load via watermark | **Lakeflow Declarative Pipelines `AUTO CDC`** | Delta `MERGE INTO` + control table / `dbutils.jobs.taskValues` | +| CDC / SQL Server change tracking | **Lakeflow Connect** or **`AUTO CDC`** | Structured Streaming over the change feed | +| Land + process files | **Auto Loader** (`cloudFiles`, file-notification mode) | — | +| Metadata-driven bulk copy (Lookup→ForEach→Copy) | **Lakeflow Connect** (multi-table) or a parameterized **Lakeflow Jobs** for-each task | — | +| Parent/child `ExecutePipeline` fan-out | **Lakeflow Jobs** for-each task + run-job task + job parameters | — | +| SCD Type 2 (data flow) | **Lakeflow Declarative Pipelines `AUTO CDC`** (SCD Type 2) | — | +| Staged load + stored-proc transform | Spark write to **Delta** + post-load step | — | +| REST API pagination | Python ingestion notebook (requests-based) | Lakeflow Connect SaaS connector if one fits | +| Custom logging / observability tier | **system tables (`system.lakeflow.*`) + native job notifications + AI/BI dashboard** | — | +| Run-state / control tables | Lakeflow job & task run state + `dbutils.jobs.taskValues` | — | +| Clone family (many near-identical pipelines) | one **parameterized Lakeflow Job** invoked N times | — | + +**Emit current names, not legacy ones:** Lakeflow Jobs (was Databricks Workflows), Lakeflow +Declarative Pipelines (was Delta Live Tables/DLT), `AUTO CDC` (was `APPLY CHANGES INTO`), Declarative +Automation Bundles (was Databricks Asset Bundles), AI/BI dashboards (was Lakeview), `system.lakeflow` +(was `system.workflow`). + +**Then author the insights (shared method).** With this deep-dive and pattern vocabulary in hand, +author and merge the `insights` object by following the source-neutral "Author and merge agentic +insights" step in the parent `SKILL.md`. The insight schema and the authoring method are shared +across sources; only the deep-dive and the construct mappings above are ADF-specific. diff --git a/skills/flowx-discover/sources/airflow.md b/skills/flowx-discover/sources/airflow.md index 190cb44..fc5ade1 100644 --- a/skills/flowx-discover/sources/airflow.md +++ b/skills/flowx-discover/sources/airflow.md @@ -69,3 +69,44 @@ that are **not** handled (dynamic TaskGroup mapping, shared multi-DAG bundle), s [`../../flowx-convert/sources/airflow-coverage.md`](../../flowx-convert/sources/airflow-coverage.md). Callables reading Airflow task context (`**context` / `ti`) or XCom, and runtime-branching decorators, are routed to placeholders for manual/agentic translation rather than converted. + +## Insights — deep-dive & pattern vocabulary + +Reference for the shared agentic-insights step (parent `SKILL.md` Step 5, "Author and merge agentic +insights"). Do this deep-dive before authoring insights for any DAG. + +**Deep-dive the DAG source.** The inventory is a deterministic skeleton (task types, strategy, +dependencies); the *why* and *how* live in the **DAG source** — the `.py` files under the +`--source-path` you discovered from. Read the DAG module for any pipeline you write about: task +callables (`PythonOperator` bodies), operator arguments, templated params, hooks / connections, and +`set_upstream` / `>>` dependencies. Recurse into `TaskGroup`s and dynamically mapped (`.expand`) +tasks. The parser already extracts operators, `>>` / `<<` edges, `schedule_interval`, and inline +callables (see "How it works" above), so read the source for the intent the static parse can't +capture — what a callable actually *does*, what a hook connects to, and why the tasks are ordered as +they are. + +**Airflow operators → Databricks** — a reference menu, NOT an allowlist; the target side uses current +product names, so reach past it whenever a better or newer fit exists. Flag `simplification_pattern: +true` only on entries that use a distinctive capability, never on the plain-orchestration fallback. + +| DAG uses… | Simplifying target — `simplification_pattern: true` (rank first) | Fallback — `false` | +|---|---|---| +| DB extract via `MsSqlOperator` / `JdbcOperator` / custom hook | **Lakeflow Connect** managed connector (change-tracking/CDC → Delta) | JDBC read + `MERGE INTO` | +| Incremental load w/ XCom or Variable watermark | **Lakeflow Declarative Pipelines `AUTO CDC`** | Delta `MERGE INTO` + `dbutils.jobs.taskValues` | +| File sensor + load (`*FileSensor` → transform) | **Auto Loader** (`cloudFiles`, file-notification mode) | — | +| `SparkSubmitOperator` / `DatabricksSubmitRunOperator` | native **Lakeflow Job** task (notebook / JAR / Python) | — | +| `PythonOperator` glue / bespoke script | notebook or Python task in a **Lakeflow Job** | — | +| `TriggerDagRunOperator` / `ExternalTaskSensor` fan-out | **Lakeflow Jobs** run-job task + job parameters | — | +| Dynamic task mapping (`.expand`) over a list | **Lakeflow Jobs** for-each task | — | +| `BashOperator` shelling out to a script | native task (notebook / Python) driven by job parameters | — | +| Custom logging / observability via XComs or a side table | **system tables (`system.lakeflow.*`) + native job notifications + AI/BI dashboard** | — | + +**Emit current names, not legacy ones:** Lakeflow Jobs (was Databricks Workflows), Lakeflow +Declarative Pipelines (was Delta Live Tables/DLT), `AUTO CDC` (was `APPLY CHANGES INTO`), Declarative +Automation Bundles (was Databricks Asset Bundles), AI/BI dashboards (was Lakeview), `system.lakeflow` +(was `system.workflow`). + +**Then author the insights (shared method).** With this deep-dive and pattern vocabulary in hand, +author and merge the `insights` object by following the source-neutral "Author and merge agentic +insights" step in the parent `SKILL.md`. The insight schema and the authoring method are shared +across sources; only the deep-dive and the construct mappings above are Airflow-specific. From ae6fd45afed012e0679176126ccc0189d6293d94 Mon Sep 17 00:00:00 2001 From: Matthew Moorcroft Date: Thu, 10 Sep 2026 11:53:38 +0100 Subject: [PATCH 6/7] Decompose flowx-discover SKILL.md back to main's hub structure The branch had re-introduced a monolithic ADF-specific `## Workflow` into skills/flowx-discover/SKILL.md (518 lines), which main had already decomposed into a source-neutral hub (80 lines) + per-source guides. Merging as-is would have reverted main's decomposition of this one skill; every other flowx skill already matched main. Rebuild SKILL.md on main's clean hub (Identify source -> Follow source guide -> How to run -> Output artifacts (shared) -> Reference) and add ONE shared section, "Author and merge agentic insights (all sources)", holding the source-neutral insight core (schema, analysis method, pattern framework, edge model, enrich). An Airflow run now loads zero ADF walkthrough into context. Preserve the two pieces of new value that lived only in the monolith by moving them into the source guides: - sources/adf.md: the inventory `lineage` block + its explanation, and the insights read-back in the summary step. - sources/airflow.md: the insights read-back, plus a note that Airflow inventories carry no `lineage` block yet (so cross-DAG relationships use `inferred` edges). Docs only; unit suite unchanged (1224 passed / 3 skipped without the mcp extra). Co-authored-by: Isaac --- skills/flowx-discover/SKILL.md | 306 ++++------------------- skills/flowx-discover/sources/adf.md | 24 +- skills/flowx-discover/sources/airflow.md | 9 + 3 files changed, 80 insertions(+), 259 deletions(-) diff --git a/skills/flowx-discover/SKILL.md b/skills/flowx-discover/SKILL.md index 6191143..53a30ea 100644 --- a/skills/flowx-discover/SKILL.md +++ b/skills/flowx-discover/SKILL.md @@ -2,8 +2,9 @@ name: flowx-discover description: > Parse a source orchestrator's pipeline definitions (Azure Data Factory, Apache Airflow) into a - typed inventory that classifies every task as deterministic, agentic, or unsupported. Phase 1 of - the flowx migration workflow; routes to a source-specific guide. + typed inventory that classifies every task as deterministic, agentic, or unsupported, then author + agentic insights (intent, ranked target patterns, cross-pipeline relationships) over it. Phase 1 + of the flowx migration workflow; routes to a source-specific guide. triggers: - "discover pipelines" - "discover ADF" @@ -58,187 +59,36 @@ Both paths are the same across sources; only `--source` and the source path diff `--source-path` is the generic flag (each source also accepts its own alias, e.g. `--adf-source-path`); both normalise to the phase's `--source-dir`. `--source` is required. -## Workflow +## Output artifacts (shared across sources) -Follow these steps in order: - -### Step 1 — Determine the ADF source path - -Ask the user for the location of their ADF JSON exports. Accept either: -- A Unity Catalog volume path (e.g., `/Volumes/main/default/adf_export`) -- A local directory path (e.g., `./adf_export/` or `/tmp/adf_json/`) - -The directory should contain subdirectories or files for: -- `pipeline/` or `pipelines/` — pipeline definition JSON files -- `dataset/` or `datasets/` — dataset definition JSON files (optional) -- `linkedService/` or `linked_services/` — linked service JSON files (optional) -- `trigger/` or `triggers/` — trigger definition JSON files (optional) - -### Step 2 — Download from UC volumes if needed - -If the source path starts with `/Volumes/`, the files live in a Unity Catalog volume and must be downloaded to a local temp directory first. - -Use the `databricks-execution-compute` skill to run the following on the Databricks workspace: - -```python -import os, json, shutil, tempfile - -volume_path = "" -local_dir = tempfile.mkdtemp(prefix="adf_ingest_") - -# Copy from volume to local -for root, dirs, files in os.walk(volume_path): - for f in files: - if f.endswith(".json"): - src = os.path.join(root, f) - rel = os.path.relpath(src, volume_path) - dst = os.path.join(local_dir, rel) - os.makedirs(os.path.dirname(dst), exist_ok=True) - shutil.copy2(src, dst) - -print(f"Downloaded ADF files to: {local_dir}") -``` - -Alternatively, use the Databricks CLI: -```bash -databricks fs cp -r "dbfs:" "" --overwrite -``` - -Set the working source directory to the local temp path for subsequent steps. - -### Step 3 — Run the deterministic parser - -Run the discover phase via the adapter's unified phase runner (recommended): - -```bash -"$PY" -m flowx.adapter discover \ - --adf-source-path \ - --output-dir \ - [--pipeline ] -``` - -`--adf-source-path` is accepted as an alias of `--source-dir` (it matches the -`adf_source_path` input option). This forwards to, and is equivalent to, running -the loader directly: - -```bash -"$PY" -m flowx.parser.adf_loader \ - --source-dir --output-dir [--pipeline ] -``` - -Where: -- `` is the root of the flowx plugin (the directory containing `src/`) -- `` is the local directory containing ADF JSON files -- `` is the **single shared migration output directory** used by all three phases - (default: `./flowx_output`). Discover writes its artifacts into the `metadata/` subfolder. -- `` (optional) — when provided, filters to only the named pipeline. When omitted, all pipelines in the source directory are included. - -**Always pass `--pipeline` when the user has specified a specific pipeline to migrate.** This ensures the inventory and all downstream phases are scoped to only that pipeline. - -This produces, under `/metadata/`: -- `inventory.json` — the classified activity inventory -- `profile_report.csv` — one row per pipeline with a complexity assessment (see Step 4b) -- `.arm.json` — the verbatim original ADF/ARM source for each pipeline (provenance) - -### Step 4 — Read and validate the inventory - -Read the generated `/metadata/inventory.json` file. It has this structure: - -```json -{ - "source_dir": "/path/to/adf/json", - "generated_at": "2026-04-07T12:00:00Z", - "pipelines": [ - { - "name": "PipelineName", - "file": "pipeline/PipelineName.json", - "activities": [ - { - "name": "CopyFromBlob", - "type": "Copy", - "strategy": "deterministic", - "translator": "copy.py" - }, - { - "name": "RunDataFlow", - "type": "ExecuteDataFlow", - "strategy": "agentic" - } - ] - } - ], - "summary": { - "pipeline_count": 12, - "activity_count": 47, - "deterministic_count": 35, - "agentic_count": 10, - "unsupported_count": 2, - "coverage_pct": 95.7 - }, - "lineage": { - "control_edges": [ - { - "caller_pipeline": "ETL_Main", - "callee_pipeline": "Load_Dim_Customer", - "activity_name": "Run Customer Load", - "wait_on_completion": true - } - ], - "data_edges": [ - { - "dataset_name": "curated_customer", - "identity": "abfss://curated/customer", - "producer_pipeline": "Load_Dim_Customer", - "producer_activity": "WriteCustomer", - "consumer_pipeline": "Build_Sales_Mart", - "consumer_activity": "ReadCustomer", - "match_kind": "identity", - "match_key": "abfss://curated/customer" - } - ] - } -} -``` - -The `lineage` block records the cross-pipeline edges the discover phase -recovered — `control_edges` (one pipeline invokes another via ExecutePipeline; -identified by `activity_name`) and `data_edges` (one pipeline writes a dataset -another reads; identified by `match_key`). Step 5 annotates these edges, so it -depends on this block being present. When a factory has no edges of a kind, its -list is empty (`[]`). - -### Step 4b — Review the complexity report - -`/metadata/profile_report.csv` carries one row per pipeline with a migration-complexity -assessment. Columns: - -| Column | Meaning | +All under the shared `/metadata/` folder: + +| File | Description | |---|---| -| `pipeline` | Pipeline name | -| `activities` | Total activities (including nested ForEach/If/Switch children) | -| `datasets` | Distinct datasets the pipeline references | -| `linked_services` | Distinct linked services (activity-level + via referenced datasets) | -| `collapsible_patterns` | Number of motif patterns detected (auto-collapsible during convert) | -| `databricks_native_activities` | Notebook / SparkJar / SparkPython / Job activities (simplest) | -| `control_flow_activities` | ForEach / If / Switch / SetVariable / AppendVariable / Filter / Wait / Until | -| `other_activities` | Everything else — Copy, Web, Lookup, agentic types (hardest) | -| `complexity_score` | Weighted score: native×1 + control×2 + other×3 + datasets + linked_services + collapsible_patterns | -| `complexity_size` | T-shirt size from the score: **S** ≤5, **M** ≤15, **L** ≤30, **XL** >30 | - -Use it to set expectations: S/M pipelines are largely deterministic; L/XL pipelines (many "other" -activities, datasets, or linked services) warrant closer review and more agentic translation. - -### Step 5 — Author and merge agentic insights - -The deterministic inventory records *what* each pipeline contains; it cannot -record *what the factory is trying to do* or *how the pipelines relate as a -system*. Author that judgment now and merge it into `inventory.json` under an -`insights` key. This always runs. - -**This step is source-neutral — it runs for every source.** The insight *schema*, the -*analysis method*, and the *pattern framework* below are shared; the source-specific inputs -(how to deep-dive the source, and its construct→Databricks pattern vocabulary) come from the -"Insights — deep-dive & pattern vocabulary" section of your `sources/.md`. +| `metadata/inventory.json` | Classified activity inventory (later enriched with agentic `insights`) for the convert phase | +| `metadata/profile_report.csv` | Per-pipeline complexity report (counts + T-shirt size) | +| `metadata/.arm.json` | (ADF) Verbatim original source for each pipeline (provenance) | + +The inventory classifies every task into one of three strategies: + +- **Deterministic** — a built-in translator exists; converted without an LLM. +- **Agentic** — requires LLM-assisted translation from the source definition. +- **Unsupported** — no known translation path; needs manual intervention. + +After classification, discovery also **authors agentic insights** over the inventory and merges +them under an `insights` key — see *Author and merge agentic insights* below. This runs for every +source; the source-specific inputs come from each `sources/.md`. + +## Author and merge agentic insights (all sources) + +The deterministic inventory records *what* each pipeline contains; it cannot record *what the +factory is trying to do* or *how the pipelines relate as a system*. Author that judgment now and +merge it into `inventory.json` under an `insights` key. This always runs. + +**This step is source-neutral — it runs for every source.** The insight *schema*, the *analysis +method*, and the *pattern framework* below are shared; the source-specific inputs (how to deep-dive +the source, and its construct→Databricks pattern vocabulary) come from the "Insights — deep-dive & +pattern vocabulary" section of your `sources/.md`. 1. **Read** the just-written `inventory.json` (`pipelines`, `lineage`, `summary`) and `profile_report.csv`. **Then, before authoring, deep-dive the source.** The @@ -364,15 +214,14 @@ system*. Author that judgment now and merge it into `inventory.json` under an state). If a pipeline does real domain work alongside the boilerplate, migrate it normally — do not tell the reader to delete real logic. - **Collapse clone families.** Cluster pipelines by their activity - *signature* (ordered activity types) and shared child-edge set across the + *signature* (ordered activity/task types) and shared child-edge set across the whole inventory. Where a family of near-identical pipelines exists, emit **one** insight (anchored on a representative pipeline that exists in the inventory) that names the family and its count, recommends collapsing the N clones into a **single parameterized job invoked N times**, and - quantifies the win (e.g. "14 `LAAE_ingest_*` pipelines, identical - `[IfCondition, IfCondition, ExecutePipeline×4]` signature → 1 parameterized - job"). List the members in `conversion_notes`. This supersedes writing N - near-duplicate per-pipeline notes. + quantifies the win (e.g. "14 near-identical ingest pipelines, identical + activity signature → 1 parameterized job"). List the members in + `conversion_notes`. This supersedes writing N near-duplicate per-pipeline notes. - `pipeline_relationships[]` — characterize **how data and control flow between the pipelines**, whatever the mechanism. Each relationship carries `from_pipeline` and `to_pipeline` (both must be pipeline names that exist in @@ -410,14 +259,13 @@ system*. Author that judgment now and merge it into `inventory.json` under an - *Data-in-code:* one pipeline's notebook writes a table another's notebook reads (no declared dataset, so `data_edges` never saw it). - *Ordering dependency:* a producer→consumer hand-off expressed only as - sibling `dependsOn` inside a parent orchestrator, which the deterministic + sibling ordering inside a parent orchestrator, which the deterministic phase did not emit as a cross-pipeline edge. - *Shared control/config asset, external trigger, message queue,* or any other real coupling flowx could not represent. - - *Near-miss (this is an annotation, not inferred):* pipeline A calls B via - ExecutePipeline and that call is already a `control_edges` entry — even - though B then does its real work in a notebook, the coupling itself was - recorded, so annotate it. + - *Near-miss (this is an annotation, not inferred):* pipeline A invokes B and + that call is already a `control_edges` entry — even though B then does its + real work in a notebook, the coupling itself was recorded, so annotate it. - **Authoring rules:** reference only pipeline names that exist in the inventory; an annotation edge (`control`/`data`) must echo a real lineage edge (annotate, don't rediscover); an `inferred` edge must carry non-empty @@ -441,73 +289,10 @@ system*. Author that judgment now and merge it into `inventory.json` under an 4. **On `ok:false`** the tool did **not** write the file: read `violations`, fix the offending pipeline name / lineage edge / field, and call `enrich` again. - On `ok:true` the `insights` key is now merged into `inventory.json`. - -### Step 6 — Present the summary - -Display a summary table to the user: - -``` -ADF Discovery Summary -===================== -Pipelines parsed: 12 -Total activities: 47 - -Strategy Breakdown: - Deterministic: 35 (74.5%) - Agentic: 10 (21.3%) - Unsupported: 2 ( 4.3%) - -Coverage: 95.7% -``` - -Then surface the authored judgment so the user sees *what the factory does*, not -just coverage numbers: print the factory `overview`, and for each -`pipeline_insights` entry its `pattern_name` / `intent` and its top -`recommended_patterns` (ranked simplification-first). Read these back from the -enriched `inventory.json`. - -### Step 7 — Detail agentic activities - -For activities classified as `agentic`, explain that each is translated by the agent using LLM-assisted reasoning from the activity's ARM JSON (no built-in deterministic translator exists for these types): - -| Activity | Type | Handling | -|---|---|---| -| RunDataFlow | ExecuteDataFlow | Agentic (LLM-assisted) | -| BranchLogic | Switch | Agentic (LLM-assisted) | -| ... | ... | ... | - -### Step 8 — Warn about unsupported activities - -For activities classified as `unsupported`, warn the user clearly: - -``` -WARNING: The following activities have no automated translation path: - - Pipeline "ETL_Main" / Activity "RunSSIS" (ExecuteSSISPackage) - Recommendation: Manual conversion to PySpark notebook required. -``` - -### Step 9 — Confirm output location - -Tell the user where the metadata files were written (`/metadata/`: inventory.json, profile_report.csv, and the per-pipeline `.arm.json`), summarise the complexity sizes, and confirm they can proceed to the `convert` phase using the same ``. - -## Examples - -- "Discover my ADF pipelines from /Volumes/main/default/adf_export" -- "Parse ADF definitions from ./tests/resources/json/" -- "Load the ADF pipeline JSON files and show me the inventory" -- "Import pipelines from /tmp/customer_adf_export" -- "Discover only the pl_demo_01 pipeline from /Volumes/main/default/adf_export" - -## Output Artifacts - -All under the shared `/metadata/` folder: - -| File | Description | -|---|---| -| `metadata/inventory.json` | Classified activity inventory for the convert phase | -| `metadata/profile_report.csv` | Per-pipeline complexity report (counts + T-shirt size) | -| `metadata/.arm.json` | Verbatim original ADF/ARM source for each pipeline | + On `ok:true` the `insights` key is now merged into `inventory.json`. Present the + authored judgment back to the user (the factory `overview`, and each + `pipeline_insights` entry's `pattern_name` / `intent` and top ranked + `recommended_patterns`) as part of the source guide's summary step. ## Future considerations @@ -516,3 +301,8 @@ large factories, revisit partitioning the authoring across subagents keyed on lineage clusters (the connected components of the combined control/data-edge graph), so each subagent reasons about one coherent subsystem. Out of scope for now — always enrich in one pass. + +## Reference + +- `sources/adf.md` — Azure Data Factory discovery (ARM JSON, UC-volume download, complexity report) + ADF insight deep-dive & pattern vocabulary +- `sources/airflow.md` — Apache Airflow discovery (DAG `.py` parsing, operator classification) + Airflow insight deep-dive & pattern vocabulary diff --git a/skills/flowx-discover/sources/adf.md b/skills/flowx-discover/sources/adf.md index 9a59889..c908898 100644 --- a/skills/flowx-discover/sources/adf.md +++ b/skills/flowx-discover/sources/adf.md @@ -57,10 +57,27 @@ Read `/metadata/inventory.json`: } ], "summary": {"pipeline_count": 12, "activity_count": 47, "deterministic_count": 35, - "agentic_count": 10, "unsupported_count": 2, "coverage_pct": 95.7} + "agentic_count": 10, "unsupported_count": 2, "coverage_pct": 95.7}, + "lineage": { + "control_edges": [ + {"caller_pipeline": "ETL_Main", "callee_pipeline": "Load_Dim_Customer", + "activity_name": "Run Customer Load", "wait_on_completion": true} + ], + "data_edges": [ + {"dataset_name": "curated_customer", "producer_pipeline": "Load_Dim_Customer", + "consumer_pipeline": "Build_Sales_Mart", "match_kind": "identity", + "match_key": "abfss://curated/customer"} + ] + } } ``` +The `lineage` block records the cross-pipeline edges the discover phase recovered from the ARM — +`control_edges` (one pipeline invokes another via `ExecutePipeline`; identified by `activity_name`) +and `data_edges` (one pipeline writes a dataset another reads; identified by `match_key`). The +shared insights step annotates these edges (`edge_type: control` / `data`), so it depends on this +block being present; when a factory has no edges of a kind, its list is empty (`[]`). + ## Step 4b — Review the complexity report `/metadata/profile_report.csv` has one row per pipeline: `pipeline`, `activities`, @@ -83,6 +100,11 @@ Strategy Breakdown: Coverage: 95.7% ``` +Then, after the shared insights step has enriched `inventory.json`, surface the authored judgment so +the user sees *what the factory does*, not just coverage numbers: print the factory `overview`, and +for each `pipeline_insights` entry its `pattern_name` / `intent` and its top `recommended_patterns` +(ranked simplification-first). + ## Step 6 — Detail agentic activities For `agentic` activities, explain that each is translated by the agent using LLM-assisted reasoning diff --git a/skills/flowx-discover/sources/airflow.md b/skills/flowx-discover/sources/airflow.md index fc5ade1..f987c07 100644 --- a/skills/flowx-discover/sources/airflow.md +++ b/skills/flowx-discover/sources/airflow.md @@ -38,6 +38,10 @@ Read `/metadata/inventory.json` (`"source": "airflow"`). Each pipeli tasks with a `strategy`. `metadata/profile_report.csv` carries one row per DAG (`pipeline`, `activities`, `complexity_size`). +Airflow inventories do **not** yet carry a `lineage` block (deterministic cross-pipeline lineage is +ADF-only today), so in the shared insights step cross-DAG relationships use **`inferred`** edges +rather than `control` / `data` annotations. + ## Step 4 — Present the summary ``` @@ -50,6 +54,11 @@ Total tasks: 8 Coverage: 87.5% ``` +Then, after the shared insights step has enriched `inventory.json`, surface the authored judgment so +the user sees *what the DAGs do*, not just coverage numbers: print the factory `overview`, and for +each `pipeline_insights` entry its `pattern_name` / `intent` and its top `recommended_patterns` +(ranked simplification-first). + ## Step 5 — Detail agentic tasks For `agentic` tasks, name the operator that has no deterministic mapping yet (e.g. a custom or From d28275af39fe07c8bbc9c01887cc4bdeb1836b32 Mon Sep 17 00:00:00 2001 From: Matthew Moorcroft Date: Thu, 10 Sep 2026 12:01:16 +0100 Subject: [PATCH 7/7] Keep #42 focused on agentic insights: drop deterministic-lineage docs Deterministic lineage (the inventory `lineage` block) is owned by the separate lineage PR that #42 builds on top of; it is not #42's to document. The previous commit had pulled the `lineage` block + its explanation into sources/adf.md and a lineage-state note into sources/airflow.md -- scope creep into the lineage PR. Revert both: sources/adf.md's inventory example returns to main's (no `lineage` block) and the airflow.md discovery step drops the lineage note. The discover docs on this branch are now main + insights-only additions. The shared insights authoring section still *consumes* lineage (the annotation vs inferred edge model resolves against `lineage.control_edges` / `data_edges`) -- a dependency on the lineage PR, not documentation of it. Docs only; unit suite unchanged (1224 passed / 3 skipped without the mcp extra). Co-authored-by: Isaac --- skills/flowx-discover/sources/adf.md | 19 +------------------ skills/flowx-discover/sources/airflow.md | 4 ---- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/skills/flowx-discover/sources/adf.md b/skills/flowx-discover/sources/adf.md index c908898..d089130 100644 --- a/skills/flowx-discover/sources/adf.md +++ b/skills/flowx-discover/sources/adf.md @@ -57,27 +57,10 @@ Read `/metadata/inventory.json`: } ], "summary": {"pipeline_count": 12, "activity_count": 47, "deterministic_count": 35, - "agentic_count": 10, "unsupported_count": 2, "coverage_pct": 95.7}, - "lineage": { - "control_edges": [ - {"caller_pipeline": "ETL_Main", "callee_pipeline": "Load_Dim_Customer", - "activity_name": "Run Customer Load", "wait_on_completion": true} - ], - "data_edges": [ - {"dataset_name": "curated_customer", "producer_pipeline": "Load_Dim_Customer", - "consumer_pipeline": "Build_Sales_Mart", "match_kind": "identity", - "match_key": "abfss://curated/customer"} - ] - } + "agentic_count": 10, "unsupported_count": 2, "coverage_pct": 95.7} } ``` -The `lineage` block records the cross-pipeline edges the discover phase recovered from the ARM — -`control_edges` (one pipeline invokes another via `ExecutePipeline`; identified by `activity_name`) -and `data_edges` (one pipeline writes a dataset another reads; identified by `match_key`). The -shared insights step annotates these edges (`edge_type: control` / `data`), so it depends on this -block being present; when a factory has no edges of a kind, its list is empty (`[]`). - ## Step 4b — Review the complexity report `/metadata/profile_report.csv` has one row per pipeline: `pipeline`, `activities`, diff --git a/skills/flowx-discover/sources/airflow.md b/skills/flowx-discover/sources/airflow.md index f987c07..963d395 100644 --- a/skills/flowx-discover/sources/airflow.md +++ b/skills/flowx-discover/sources/airflow.md @@ -38,10 +38,6 @@ Read `/metadata/inventory.json` (`"source": "airflow"`). Each pipeli tasks with a `strategy`. `metadata/profile_report.csv` carries one row per DAG (`pipeline`, `activities`, `complexity_size`). -Airflow inventories do **not** yet carry a `lineage` block (deterministic cross-pipeline lineage is -ADF-only today), so in the shared insights step cross-DAG relationships use **`inferred`** edges -rather than `control` / `data` annotations. - ## Step 4 — Present the summary ```