📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-06-23 16:03:00 +00:00
parent c4c6a41c21
commit 59e15f8999
557 changed files with 10501 additions and 3168 deletions
@@ -0,0 +1,66 @@
"""Path guards for local Monte Carlo template manifests."""
from __future__ import annotations
import json
import os
from pathlib import Path
def _allow_external_paths() -> bool:
return os.getenv("MCD_ALLOW_EXTERNAL_PATHS", "").lower() in {"1", "true", "yes"}
def _is_relative_to(path: Path, root: Path) -> bool:
try:
path.relative_to(root)
return True
except ValueError:
return False
def _resolve_local_path(raw_path: str, *, expect_file: bool = False, create_parent: bool = False) -> Path:
value = str(raw_path).strip()
if not value or "\0" in value:
raise ValueError("Path must be a non-empty filesystem path")
base = Path.cwd().resolve()
candidate = Path(value).expanduser()
resolved = (candidate if candidate.is_absolute() else base / candidate).resolve()
if not _allow_external_paths() and not _is_relative_to(resolved, base):
raise ValueError(f"Path must stay under the current working directory: {raw_path!r}")
if expect_file and not resolved.is_file():
raise FileNotFoundError(f"Input file not found: {resolved}")
if create_parent:
resolved.parent.mkdir(parents=True, exist_ok=True)
return resolved
def safe_input_json_path(raw_path: str) -> Path:
path = _resolve_local_path(raw_path, expect_file=True)
if path.suffix.lower() != ".json":
raise ValueError(f"Input manifest must be a .json file: {path}")
return path
def safe_output_json_path(raw_path: str) -> Path:
path = _resolve_local_path(raw_path, create_parent=True)
if path.suffix.lower() != ".json":
raise ValueError(f"Output manifest must be a .json file: {path}")
return path
def safe_existing_directory(raw_path: str) -> Path:
path = _resolve_local_path(raw_path)
if not path.is_dir():
raise NotADirectoryError(f"Directory not found: {path}")
return path
def read_json_file(raw_path: str):
with safe_input_json_path(raw_path).open() as fh:
return json.load(fh)
def write_json_file(raw_path: str, payload, *, indent: int = 2, default=None) -> None:
with safe_output_json_path(raw_path).open("w") as fh:
json.dump(payload, fh, indent=indent, default=default)
@@ -14,8 +14,9 @@ from __future__ import annotations
import argparse
import os
from collect_metadata import collect
from collect_metadata import _require_bq_identifier, collect
from push_metadata import push
from _safe_paths import safe_output_json_path
def main() -> None:
@@ -49,21 +50,28 @@ def main() -> None:
if missing:
parser.error(f"Missing required push arguments/env vars: {missing}")
manifest_path = str(safe_output_json_path(args.manifest_file))
push_result_path = str(safe_output_json_path(args.push_result_file))
args.project_id = _require_bq_identifier(args.project_id, "project_id")
args.datasets = [_require_bq_identifier(d, "dataset") for d in args.datasets or []] or None
args.tables = [_require_bq_identifier(t, "table") for t in args.tables or []] or None
collect(
project_id=args.project_id,
datasets=args.datasets,
tables=args.tables,
only_freshness_and_volume=args.only_freshness_and_volume,
output_file=args.manifest_file,
output_file=manifest_path,
)
push(
input_file=args.manifest_file,
input_file=manifest_path,
resource_uuid=args.resource_uuid,
key_id=args.key_id,
key_token=args.key_token,
batch_size=args.batch_size,
output_file=args.push_result_file,
output_file=push_result_path,
)
@@ -15,6 +15,7 @@ import os
from collect_query_logs import LOOKBACK_HOURS, LOOKBACK_LAG_HOURS, collect
from push_query_logs import push
from _safe_paths import safe_output_json_path
def main() -> None:
@@ -43,20 +44,23 @@ def main() -> None:
if missing:
parser.error(f"Missing required push arguments/env vars: {missing}")
manifest_path = str(safe_output_json_path(args.manifest_file))
push_result_path = str(safe_output_json_path(args.push_result_file))
collect(
project_id=args.project_id,
lookback_hours=args.lookback_hours,
lookback_lag_hours=args.lookback_lag_hours,
output_file=args.manifest_file,
output_file=manifest_path,
)
push(
input_file=args.manifest_file,
input_file=manifest_path,
resource_uuid=args.resource_uuid,
key_id=args.key_id,
key_token=args.key_token,
batch_size=args.batch_size,
output_file=args.push_result_file,
output_file=push_result_path,
)
@@ -26,14 +26,24 @@ import argparse
import json
import logging
import os
import re
from datetime import datetime, timezone
from google.cloud import bigquery
from _safe_paths import safe_existing_directory, safe_input_json_path, safe_output_json_path, write_json_file
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
RESOURCE_TYPE = "bigquery"
_BQ_IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9_-]+$")
def _require_bq_identifier(value: str, field: str) -> str:
value = str(value).strip()
if not value or not _BQ_IDENTIFIER_RE.fullmatch(value):
raise ValueError(f"Invalid BigQuery {field}: {value!r}")
return value
# BigQuery type → Monte Carlo canonical type
BQ_TYPE_MAP: dict[str, str] = {
@@ -71,16 +81,20 @@ def _fetch_iceberg_tables(
tables: list[str] | None = None,
) -> list[dict]:
"""Query TABLE_STORAGE for BigLake (Iceberg) tables."""
project_id = _require_bq_identifier(project_id, "project_id")
datasets = [_require_bq_identifier(d, "dataset") for d in datasets or []] or None
tables = [_require_bq_identifier(t, "table") for t in tables or []] or None
conditions = [
"managed_table_type = 'BIGLAKE'",
"deleted = FALSE",
]
query_parameters = []
if datasets:
ds_list = ", ".join(f"'{d}'" for d in datasets)
conditions.append(f"table_schema IN ({ds_list})")
conditions.append("table_schema IN UNNEST(@datasets)")
query_parameters.append(bigquery.ArrayQueryParameter("datasets", "STRING", datasets))
if tables:
tbl_list = ", ".join(f"'{t}'" for t in tables)
conditions.append(f"table_name IN ({tbl_list})")
conditions.append("table_name IN UNNEST(@tables)")
query_parameters.append(bigquery.ArrayQueryParameter("tables", "STRING", tables))
where = " AND ".join(conditions)
query = f"""
@@ -96,7 +110,8 @@ def _fetch_iceberg_tables(
ORDER BY table_schema, table_name
"""
log.info("Querying TABLE_STORAGE for Iceberg tables ...")
rows = list(client.query(query).result())
job_config = bigquery.QueryJobConfig(query_parameters=query_parameters)
rows = list(client.query(query, job_config=job_config).result())
log.info("Found %d Iceberg table(s).", len(rows))
return [dict(row) for row in rows]
@@ -108,18 +123,24 @@ def _fetch_columns(
table_name: str,
) -> list[dict]:
"""Fetch column metadata for a specific table."""
project_id = _require_bq_identifier(project_id, "project_id")
dataset = _require_bq_identifier(dataset, "dataset")
table_name = _require_bq_identifier(table_name, "table")
query = f"""
SELECT column_name, data_type, ordinal_position, is_nullable, column_default
FROM `{project_id}.{dataset}.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name = '{table_name}'
WHERE table_name = @table_name
ORDER BY ordinal_position
"""
job_config = bigquery.QueryJobConfig(
query_parameters=[bigquery.ScalarQueryParameter("table_name", "STRING", table_name)]
)
return [
{
"name": row["column_name"],
"type": map_bq_type(row["data_type"]),
}
for row in client.query(query).result()
for row in client.query(query, job_config=job_config).result()
]
@@ -155,6 +176,9 @@ def collect(
omits fields from the manifest. Use this for periodic hourly pushes
after the initial full metadata push.
"""
project_id = _require_bq_identifier(project_id, "project_id")
datasets = [_require_bq_identifier(d, "dataset") for d in datasets or []] or None
tables = [_require_bq_identifier(t, "table") for t in tables or []] or None
client = bigquery.Client(project=project_id) # ← SUBSTITUTE: adjust auth if needed
if only_freshness_and_volume:
@@ -200,8 +224,7 @@ def collect(
"collected_at": datetime.now(timezone.utc).isoformat(),
"assets": assets,
}
with open(output_file, "w") as fh:
json.dump(manifest, fh, indent=2)
write_json_file(output_file, manifest)
log.info("Manifest written to %s (%d assets)", output_file, len(assets))
return manifest
@@ -23,6 +23,7 @@ import os
from datetime import datetime, timedelta, timezone
from google.cloud import bigquery
from _safe_paths import safe_existing_directory, safe_input_json_path, safe_output_json_path, write_json_file
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
@@ -113,8 +114,7 @@ def collect(
"query_log_count": len(entries),
"queries": entries,
}
with open(output_file, "w") as fh:
json.dump(manifest, fh, indent=2)
write_json_file(output_file, manifest)
log.info("Query log manifest written to %s", output_file)
return manifest
@@ -33,6 +33,7 @@ from pycarlo.features.ingestion.models import (
AssetVolume,
RelationalAsset,
)
from _safe_paths import safe_existing_directory, safe_input_json_path, safe_output_json_path, read_json_file, write_json_file
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
@@ -92,8 +93,7 @@ def push(
"""Read a metadata manifest and push assets to Monte Carlo in batches."""
endpoint = _ENDPOINT
log.info("Using endpoint: %s", endpoint)
with open(input_file) as fh:
manifest = json.load(fh)
manifest = read_json_file(input_file)
asset_dicts = manifest.get("assets", [])
resource_type = manifest.get("resource_type", RESOURCE_TYPE)
@@ -147,8 +147,7 @@ def push(
"batch_count": total_batches,
"batch_size": batch_size,
}
with open(output_file, "w") as fh:
json.dump(push_result, fh, indent=2)
write_json_file(output_file, push_result)
log.info("Push result written to %s", output_file)
return push_result
@@ -32,6 +32,7 @@ from dateutil.parser import isoparse
from pycarlo.core import Client, Session
from pycarlo.features.ingestion import IngestionService
from pycarlo.features.ingestion.models import QueryLogEntry
from _safe_paths import safe_existing_directory, safe_input_json_path, safe_output_json_path, read_json_file, write_json_file
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
@@ -95,8 +96,7 @@ def push(
endpoint = _ENDPOINT
log.info("Using endpoint: %s", endpoint)
with open(input_file) as fh:
manifest = json.load(fh)
manifest = read_json_file(input_file)
queries = manifest.get("queries", [])
log_type = manifest.get("log_type", LOG_TYPE)
@@ -114,8 +114,7 @@ def push(
"batch_count": 0,
"batch_size": batch_size,
}
with open(output_file, "w") as fh:
json.dump(push_result, fh, indent=2)
write_json_file(output_file, push_result)
return push_result
batches = [entries[i : i + batch_size] for i in range(0, len(entries), batch_size)]
@@ -165,8 +164,7 @@ def push(
"batch_count": total_batches,
"batch_size": batch_size,
}
with open(output_file, "w") as fh:
json.dump(push_result, fh, indent=2)
write_json_file(output_file, push_result)
log.info("Push result written to %s", output_file)
return push_result