dataforge-07 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataforge/__init__.py +204 -0
- dataforge/__main__.py +5 -0
- dataforge/agent/__init__.py +16 -0
- dataforge/agent/providers.py +259 -0
- dataforge/agent/scratchpad.py +183 -0
- dataforge/agent/tool_actions.py +343 -0
- dataforge/bench/__init__.py +31 -0
- dataforge/bench/core.py +426 -0
- dataforge/bench/groq_client.py +386 -0
- dataforge/bench/methods.py +443 -0
- dataforge/bench/report.py +309 -0
- dataforge/bench/runner.py +247 -0
- dataforge/causal/__init__.py +21 -0
- dataforge/causal/dag.py +174 -0
- dataforge/causal/pc.py +232 -0
- dataforge/causal/root_cause.py +193 -0
- dataforge/cli/__init__.py +50 -0
- dataforge/cli/audit.py +70 -0
- dataforge/cli/bench.py +154 -0
- dataforge/cli/common.py +267 -0
- dataforge/cli/constraints.py +407 -0
- dataforge/cli/profile.py +147 -0
- dataforge/cli/release.py +166 -0
- dataforge/cli/repair.py +407 -0
- dataforge/cli/revert.py +139 -0
- dataforge/cli/watch.py +144 -0
- dataforge/datasets/__init__.py +25 -0
- dataforge/datasets/embedded/hospital/clean.csv +11 -0
- dataforge/datasets/embedded/hospital/dirty.csv +11 -0
- dataforge/datasets/real_world.py +290 -0
- dataforge/datasets/registry.py +103 -0
- dataforge/detectors/__init__.py +80 -0
- dataforge/detectors/base.py +145 -0
- dataforge/detectors/decimal_shift.py +166 -0
- dataforge/detectors/fd_violation.py +157 -0
- dataforge/detectors/type_mismatch.py +173 -0
- dataforge/engine/__init__.py +39 -0
- dataforge/engine/repair.py +905 -0
- dataforge/env/__init__.py +22 -0
- dataforge/env/environment.py +883 -0
- dataforge/env/observation.py +61 -0
- dataforge/env/openenv_core.py +161 -0
- dataforge/env/reward.py +128 -0
- dataforge/env/server.py +176 -0
- dataforge/evaluation_contract.py +76 -0
- dataforge/fixtures/hospital_10rows.csv +11 -0
- dataforge/fixtures/hospital_schema.yaml +17 -0
- dataforge/http/__init__.py +1 -0
- dataforge/http/problem.py +103 -0
- dataforge/integrations/__init__.py +1 -0
- dataforge/integrations/dbt.py +164 -0
- dataforge/observability.py +76 -0
- dataforge/py.typed +1 -0
- dataforge/release/__init__.py +1 -0
- dataforge/release/doctor.py +367 -0
- dataforge/release/full_vision.py +702 -0
- dataforge/release/gate.py +861 -0
- dataforge/release/playground_check.py +411 -0
- dataforge/repair_contract.py +468 -0
- dataforge/repairers/__init__.py +88 -0
- dataforge/repairers/base.py +77 -0
- dataforge/repairers/decimal_shift.py +43 -0
- dataforge/repairers/fd_violation.py +225 -0
- dataforge/repairers/type_mismatch.py +73 -0
- dataforge/safety/__init__.py +5 -0
- dataforge/safety/adversarial/attack_01_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_02_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_03_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_04_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_05_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_06_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_07_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_08_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_09_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_10_phone_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_11_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_12_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_13_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_14_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_15_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_16_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_17_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_18_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_19_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_20_ssn_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_21_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_22_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_23_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_24_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_25_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_26_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_27_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_28_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_29_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_30_email_pii.yaml +8 -0
- dataforge/safety/adversarial/attack_31_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_32_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_33_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_34_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_35_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_36_row_delete.yaml +11 -0
- dataforge/safety/adversarial/attack_37_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_38_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_39_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_40_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_41_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_42_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_43_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_44_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_45_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_46_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_47_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_48_row_delete.yaml +7 -0
- dataforge/safety/adversarial/attack_49_row_delete.yaml +8 -0
- dataforge/safety/adversarial/attack_50_row_delete.yaml +7 -0
- dataforge/safety/constitution.py +307 -0
- dataforge/safety/constitutions/default.yaml +40 -0
- dataforge/safety/filter.py +134 -0
- dataforge/schema_inference.py +620 -0
- dataforge/stores/__init__.py +46 -0
- dataforge/stores/base.py +73 -0
- dataforge/stores/cloud.py +78 -0
- dataforge/stores/csv.py +94 -0
- dataforge/stores/duckdb.py +313 -0
- dataforge/stores/patch_plan.py +178 -0
- dataforge/stores/registry.py +82 -0
- dataforge/stores/repair.py +121 -0
- dataforge/stores/revert.py +22 -0
- dataforge/stores/sql.py +27 -0
- dataforge/table.py +228 -0
- dataforge/transactions/__init__.py +34 -0
- dataforge/transactions/files.py +96 -0
- dataforge/transactions/log.py +613 -0
- dataforge/transactions/revert.py +102 -0
- dataforge/transactions/txn.py +104 -0
- dataforge/ui/__init__.py +1 -0
- dataforge/ui/profile_view.py +136 -0
- dataforge/ui/repair_diff.py +91 -0
- dataforge/verifier/__init__.py +55 -0
- dataforge/verifier/constraint_ir.py +155 -0
- dataforge/verifier/explain.py +47 -0
- dataforge/verifier/gate.py +5 -0
- dataforge/verifier/schema.py +111 -0
- dataforge/verifier/smt.py +433 -0
- dataforge_07-0.1.0.dist-info/METADATA +436 -0
- dataforge_07-0.1.0.dist-info/RECORD +150 -0
- dataforge_07-0.1.0.dist-info/WHEEL +5 -0
- dataforge_07-0.1.0.dist-info/entry_points.txt +3 -0
- dataforge_07-0.1.0.dist-info/licenses/LICENSE +176 -0
- dataforge_07-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""Release doctor checks for DataForge public-surface gates."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import tomllib
|
|
11
|
+
from dataclasses import asdict, dataclass
|
|
12
|
+
from importlib import metadata as importlib_metadata
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
EXPECTED_HF_USER = "Praneshrajan15"
|
|
17
|
+
DEFAULT_KAGGLE_CREDENTIALS = Path.home() / ".kaggle" / "credentials.json"
|
|
18
|
+
STALE_KAGGLE_JSON = Path.home() / ".kaggle" / "kaggle.json"
|
|
19
|
+
CORE_DISTRIBUTION = "dataforge_07"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class DoctorCheck:
|
|
24
|
+
"""One release doctor check result."""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
ok: bool
|
|
28
|
+
detail: str
|
|
29
|
+
metadata: dict[str, Any]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class DoctorReport:
|
|
34
|
+
"""Machine-readable release doctor report."""
|
|
35
|
+
|
|
36
|
+
ok: bool
|
|
37
|
+
checks: list[DoctorCheck]
|
|
38
|
+
scopes: list[str]
|
|
39
|
+
secrets_printed: bool = False
|
|
40
|
+
|
|
41
|
+
def to_dict(self) -> dict[str, Any]:
|
|
42
|
+
"""Return a JSON-serializable report."""
|
|
43
|
+
return asdict(self)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _check_hugging_face() -> DoctorCheck:
|
|
47
|
+
try:
|
|
48
|
+
from huggingface_hub import HfApi, get_token
|
|
49
|
+
except ImportError as exc:
|
|
50
|
+
return DoctorCheck("hugging_face", False, f"huggingface_hub missing: {exc}", {})
|
|
51
|
+
|
|
52
|
+
token_present = bool(get_token())
|
|
53
|
+
if not token_present:
|
|
54
|
+
return DoctorCheck(
|
|
55
|
+
"hugging_face",
|
|
56
|
+
False,
|
|
57
|
+
"No cached Hugging Face token found.",
|
|
58
|
+
{"expected_user": EXPECTED_HF_USER, "token_present": False},
|
|
59
|
+
)
|
|
60
|
+
try:
|
|
61
|
+
info = HfApi(token=get_token()).whoami()
|
|
62
|
+
except Exception as exc:
|
|
63
|
+
return DoctorCheck(
|
|
64
|
+
"hugging_face",
|
|
65
|
+
False,
|
|
66
|
+
f"Could not resolve Hugging Face identity: {exc}",
|
|
67
|
+
{"expected_user": EXPECTED_HF_USER, "token_present": True},
|
|
68
|
+
)
|
|
69
|
+
user = str(info.get("name", ""))
|
|
70
|
+
return DoctorCheck(
|
|
71
|
+
"hugging_face",
|
|
72
|
+
user == EXPECTED_HF_USER,
|
|
73
|
+
"Authenticated with expected Hugging Face user."
|
|
74
|
+
if user == EXPECTED_HF_USER
|
|
75
|
+
else f"Authenticated as {user!r}, expected {EXPECTED_HF_USER!r}.",
|
|
76
|
+
{"user": user, "expected_user": EXPECTED_HF_USER, "token_present": True},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _load_kaggle_oauth(path: Path) -> dict[str, Any]:
|
|
81
|
+
if path.name == "kaggle.json":
|
|
82
|
+
raise RuntimeError(
|
|
83
|
+
f"Refusing to read stale legacy Kaggle API key file: {path}. "
|
|
84
|
+
f"Use OAuth credentials at {DEFAULT_KAGGLE_CREDENTIALS}."
|
|
85
|
+
)
|
|
86
|
+
if not path.exists():
|
|
87
|
+
raise RuntimeError(f"Missing Kaggle OAuth credentials: {path}")
|
|
88
|
+
payload = json.loads(path.read_text(encoding="utf-8-sig"))
|
|
89
|
+
if not isinstance(payload, dict):
|
|
90
|
+
raise RuntimeError("Kaggle OAuth credentials must be a JSON object.")
|
|
91
|
+
required = {"refresh_token", "access_token", "access_token_expiration", "username", "scopes"}
|
|
92
|
+
missing = sorted(required - set(payload))
|
|
93
|
+
if missing:
|
|
94
|
+
raise RuntimeError("Kaggle OAuth credentials missing fields: " + ", ".join(missing))
|
|
95
|
+
if not isinstance(payload.get("username"), str) or not payload["username"]:
|
|
96
|
+
raise RuntimeError("Kaggle OAuth credentials are missing username.")
|
|
97
|
+
scopes = payload.get("scopes")
|
|
98
|
+
if not isinstance(scopes, list) or not scopes:
|
|
99
|
+
raise RuntimeError("Kaggle OAuth credentials are missing scopes.")
|
|
100
|
+
return payload
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _check_kaggle_oauth(credentials_path: Path = DEFAULT_KAGGLE_CREDENTIALS) -> DoctorCheck:
|
|
104
|
+
try:
|
|
105
|
+
payload = _load_kaggle_oauth(credentials_path)
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
return DoctorCheck(
|
|
108
|
+
"kaggle_oauth", False, str(exc), {"credential_path": str(credentials_path)}
|
|
109
|
+
)
|
|
110
|
+
return DoctorCheck(
|
|
111
|
+
"kaggle_oauth",
|
|
112
|
+
True,
|
|
113
|
+
"Kaggle OAuth credentials are present and legacy key is ignored.",
|
|
114
|
+
{
|
|
115
|
+
"credential_path": str(credentials_path),
|
|
116
|
+
"username": payload["username"],
|
|
117
|
+
"scopes_count": len(payload["scopes"]),
|
|
118
|
+
"legacy_kaggle_json_exists": STALE_KAGGLE_JSON.exists(),
|
|
119
|
+
"legacy_kaggle_json_used": False,
|
|
120
|
+
"tokens_printed": False,
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _check_kaggle_cli_clean_config(
|
|
126
|
+
credentials_path: Path = DEFAULT_KAGGLE_CREDENTIALS,
|
|
127
|
+
) -> DoctorCheck:
|
|
128
|
+
"""Verify Kaggle CLI auth through OAuth under a clean config directory."""
|
|
129
|
+
script = _project_root() / "scripts" / "preflight" / "check_kaggle_auth.py"
|
|
130
|
+
if not script.exists():
|
|
131
|
+
return DoctorCheck(
|
|
132
|
+
"kaggle_cli_clean_config",
|
|
133
|
+
False,
|
|
134
|
+
f"Kaggle auth preflight script not found: {script}",
|
|
135
|
+
{"credential_path": str(credentials_path), "tokens_printed": False},
|
|
136
|
+
)
|
|
137
|
+
command = [
|
|
138
|
+
sys.executable,
|
|
139
|
+
str(script),
|
|
140
|
+
"--kaggle-json",
|
|
141
|
+
str(credentials_path),
|
|
142
|
+
"--check-cli",
|
|
143
|
+
]
|
|
144
|
+
try:
|
|
145
|
+
result = subprocess.run(
|
|
146
|
+
command,
|
|
147
|
+
cwd=_project_root(),
|
|
148
|
+
capture_output=True,
|
|
149
|
+
text=True,
|
|
150
|
+
encoding="utf-8",
|
|
151
|
+
errors="replace",
|
|
152
|
+
timeout=90,
|
|
153
|
+
check=False,
|
|
154
|
+
)
|
|
155
|
+
except subprocess.TimeoutExpired:
|
|
156
|
+
return DoctorCheck(
|
|
157
|
+
"kaggle_cli_clean_config",
|
|
158
|
+
False,
|
|
159
|
+
"Kaggle CLI clean-config OAuth preflight timed out.",
|
|
160
|
+
{"credential_path": str(credentials_path), "tokens_printed": False},
|
|
161
|
+
)
|
|
162
|
+
ok = result.returncode == 0
|
|
163
|
+
detail = (
|
|
164
|
+
"Kaggle CLI read-only command succeeded with clean KAGGLE_CONFIG_DIR."
|
|
165
|
+
if ok
|
|
166
|
+
else "Kaggle CLI clean-config OAuth preflight failed."
|
|
167
|
+
)
|
|
168
|
+
return DoctorCheck(
|
|
169
|
+
"kaggle_cli_clean_config",
|
|
170
|
+
ok,
|
|
171
|
+
detail,
|
|
172
|
+
{
|
|
173
|
+
"credential_path": str(credentials_path),
|
|
174
|
+
"command": " ".join(command),
|
|
175
|
+
"tokens_printed": False,
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _check_cloudflare() -> DoctorCheck:
|
|
181
|
+
npx = shutil.which("npx") or shutil.which("npx.cmd")
|
|
182
|
+
if npx is None:
|
|
183
|
+
return DoctorCheck("cloudflare", False, "npx/wrangler not available on PATH.", {})
|
|
184
|
+
env_file_path = ""
|
|
185
|
+
try:
|
|
186
|
+
with tempfile.NamedTemporaryFile(delete=False) as env_file:
|
|
187
|
+
env_file_path = env_file.name
|
|
188
|
+
command = [npx, "wrangler", "whoami", "--env-file", env_file_path]
|
|
189
|
+
try:
|
|
190
|
+
result = subprocess.run(
|
|
191
|
+
command,
|
|
192
|
+
capture_output=True,
|
|
193
|
+
text=True,
|
|
194
|
+
encoding="utf-8",
|
|
195
|
+
errors="replace",
|
|
196
|
+
timeout=60,
|
|
197
|
+
check=False,
|
|
198
|
+
)
|
|
199
|
+
finally:
|
|
200
|
+
if env_file_path:
|
|
201
|
+
Path(env_file_path).unlink(missing_ok=True)
|
|
202
|
+
except FileNotFoundError:
|
|
203
|
+
return DoctorCheck("cloudflare", False, "npx/wrangler not available on PATH.", {})
|
|
204
|
+
except subprocess.TimeoutExpired:
|
|
205
|
+
return DoctorCheck("cloudflare", False, "wrangler whoami timed out.", {})
|
|
206
|
+
|
|
207
|
+
output = result.stdout + result.stderr
|
|
208
|
+
ok = result.returncode == 0 and "logged in" in output.lower()
|
|
209
|
+
has_route_scope = "workers_routes (write)" in output
|
|
210
|
+
return DoctorCheck(
|
|
211
|
+
"cloudflare",
|
|
212
|
+
ok and has_route_scope,
|
|
213
|
+
"Wrangler OAuth is logged in with Workers route scope."
|
|
214
|
+
if ok and has_route_scope
|
|
215
|
+
else "Wrangler is not logged in with the required Workers route scope.",
|
|
216
|
+
{
|
|
217
|
+
"wrangler_available": result.returncode == 0,
|
|
218
|
+
"logged_in": ok,
|
|
219
|
+
"workers_routes_write": has_route_scope,
|
|
220
|
+
"command": f"{npx} wrangler whoami --env-file <empty-env-file>",
|
|
221
|
+
"env_file_override": "empty",
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _project_root() -> Path:
|
|
227
|
+
"""Return the repository root for local core release checks."""
|
|
228
|
+
return Path(__file__).resolve().parents[2]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _check_package_boundary() -> DoctorCheck:
|
|
232
|
+
"""Verify the core wheel only includes the public DataForge namespace."""
|
|
233
|
+
pyproject_path = _project_root() / "pyproject.toml"
|
|
234
|
+
if not pyproject_path.exists():
|
|
235
|
+
try:
|
|
236
|
+
distribution = importlib_metadata.distribution(CORE_DISTRIBUTION)
|
|
237
|
+
top_level = distribution.read_text("top_level.txt") or ""
|
|
238
|
+
except importlib_metadata.PackageNotFoundError as exc:
|
|
239
|
+
return DoctorCheck(
|
|
240
|
+
"core_package_boundary",
|
|
241
|
+
False,
|
|
242
|
+
f"Could not read installed package metadata: {exc}",
|
|
243
|
+
{"distribution": CORE_DISTRIBUTION},
|
|
244
|
+
)
|
|
245
|
+
top_level_packages = sorted(line.strip() for line in top_level.splitlines() if line.strip())
|
|
246
|
+
expected_top_level = ["dataforge"]
|
|
247
|
+
return DoctorCheck(
|
|
248
|
+
"core_package_boundary",
|
|
249
|
+
top_level_packages == expected_top_level,
|
|
250
|
+
"Installed wheel exposes only the dataforge top-level package."
|
|
251
|
+
if top_level_packages == expected_top_level
|
|
252
|
+
else (
|
|
253
|
+
"Installed wheel exposes top-level packages "
|
|
254
|
+
f"{top_level_packages!r}, expected {expected_top_level!r}."
|
|
255
|
+
),
|
|
256
|
+
{"top_level": top_level_packages, "expected": expected_top_level},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
pyproject = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
|
|
261
|
+
include = pyproject["tool"]["setuptools"]["packages"]["find"]["include"]
|
|
262
|
+
except Exception as exc:
|
|
263
|
+
return DoctorCheck(
|
|
264
|
+
"core_package_boundary",
|
|
265
|
+
False,
|
|
266
|
+
f"Could not read package include contract: {exc}",
|
|
267
|
+
{"pyproject": str(pyproject_path)},
|
|
268
|
+
)
|
|
269
|
+
expected = ["dataforge", "dataforge.*"]
|
|
270
|
+
return DoctorCheck(
|
|
271
|
+
"core_package_boundary",
|
|
272
|
+
include == expected,
|
|
273
|
+
"Core wheel includes only the dataforge namespace."
|
|
274
|
+
if include == expected
|
|
275
|
+
else f"Core wheel package include list is {include!r}, expected {expected!r}.",
|
|
276
|
+
{"include": include, "expected": expected},
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _check_packaged_core_files() -> DoctorCheck:
|
|
281
|
+
"""Verify files required by source-install and wheel smoke commands exist."""
|
|
282
|
+
root = _project_root()
|
|
283
|
+
required = [
|
|
284
|
+
root / "dataforge" / "py.typed",
|
|
285
|
+
root / "dataforge" / "fixtures" / "hospital_10rows.csv",
|
|
286
|
+
root / "dataforge" / "fixtures" / "hospital_schema.yaml",
|
|
287
|
+
root / "dataforge" / "safety" / "constitutions" / "default.yaml",
|
|
288
|
+
]
|
|
289
|
+
missing = [str(path.relative_to(root)) for path in required if not path.exists()]
|
|
290
|
+
return DoctorCheck(
|
|
291
|
+
"core_packaged_files",
|
|
292
|
+
not missing,
|
|
293
|
+
"Core package data files required by smoke commands are present."
|
|
294
|
+
if not missing
|
|
295
|
+
else "Missing core package data files: " + ", ".join(missing),
|
|
296
|
+
{"missing": missing},
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _core_checks() -> list[DoctorCheck]:
|
|
301
|
+
"""Return local OSS release checks that do not require personal accounts."""
|
|
302
|
+
return [_check_package_boundary(), _check_packaged_core_files()]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _maintainer_deploy_checks(kaggle_credentials: Path) -> list[DoctorCheck]:
|
|
306
|
+
"""Return maintainer-specific deploy/auth checks."""
|
|
307
|
+
return [
|
|
308
|
+
_check_hugging_face(),
|
|
309
|
+
_check_kaggle_oauth(kaggle_credentials),
|
|
310
|
+
_check_kaggle_cli_clean_config(kaggle_credentials),
|
|
311
|
+
_check_cloudflare(),
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def run_doctor(
|
|
316
|
+
*,
|
|
317
|
+
kaggle_credentials: Path = DEFAULT_KAGGLE_CREDENTIALS,
|
|
318
|
+
core: bool = True,
|
|
319
|
+
maintainer_deploy: bool = False,
|
|
320
|
+
) -> DoctorReport:
|
|
321
|
+
"""Run selected release doctor checks."""
|
|
322
|
+
checks: list[DoctorCheck] = []
|
|
323
|
+
scopes: list[str] = []
|
|
324
|
+
if core:
|
|
325
|
+
checks.extend(_core_checks())
|
|
326
|
+
scopes.append("core")
|
|
327
|
+
if maintainer_deploy:
|
|
328
|
+
checks.extend(_maintainer_deploy_checks(kaggle_credentials))
|
|
329
|
+
scopes.append("maintainer_deploy")
|
|
330
|
+
return DoctorReport(
|
|
331
|
+
ok=all(check.ok for check in checks),
|
|
332
|
+
checks=checks,
|
|
333
|
+
scopes=scopes,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def main(argv: list[str] | None = None) -> int:
|
|
338
|
+
"""Script entrypoint used by CI and local release work."""
|
|
339
|
+
import argparse
|
|
340
|
+
|
|
341
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
342
|
+
parser.add_argument("--json", action="store_true", help="Print JSON instead of text.")
|
|
343
|
+
parser.add_argument("--kaggle-credentials", type=Path, default=DEFAULT_KAGGLE_CREDENTIALS)
|
|
344
|
+
parser.add_argument("--core", action="store_true", help="Run OSS core release checks.")
|
|
345
|
+
parser.add_argument(
|
|
346
|
+
"--maintainer-deploy",
|
|
347
|
+
action="store_true",
|
|
348
|
+
help="Run maintainer-specific deploy/auth checks.",
|
|
349
|
+
)
|
|
350
|
+
args = parser.parse_args(argv)
|
|
351
|
+
core = args.core or not args.maintainer_deploy
|
|
352
|
+
report = run_doctor(
|
|
353
|
+
kaggle_credentials=args.kaggle_credentials,
|
|
354
|
+
core=core,
|
|
355
|
+
maintainer_deploy=args.maintainer_deploy,
|
|
356
|
+
)
|
|
357
|
+
if args.json:
|
|
358
|
+
print(json.dumps(report.to_dict(), indent=2, sort_keys=True))
|
|
359
|
+
else:
|
|
360
|
+
for check in report.checks:
|
|
361
|
+
status = "ok" if check.ok else "fail"
|
|
362
|
+
print(f"{status:4} {check.name}: {check.detail}")
|
|
363
|
+
return 0 if report.ok else 2
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
if __name__ == "__main__":
|
|
367
|
+
raise SystemExit(main(sys.argv[1:]))
|