composable-data-stack 0.5.1__tar.gz → 0.5.2__tar.gz
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.
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/PKG-INFO +1 -1
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/getter.py +42 -7
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/main.py +20 -7
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/composable_data_stack.egg-info/PKG-INFO +1 -1
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/pyproject.toml +1 -1
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_getter.py +134 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_main.py +54 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/LICENSE +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/README.md +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/__init__.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/constants.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/diagnostics.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/graph.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/image_updates.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/image_verification.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/loader.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/overlay.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/planner.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/preflight.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/renderer.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resolver.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/__init__.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/contract.schema.json +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/module.schema.json +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/profile.schema.json +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/rule-schema.json +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/rule-set.json +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/secrets.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/security.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/security_common.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/state.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/up_runner.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/utils.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/validator.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/composable_data_stack.egg-info/SOURCES.txt +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/composable_data_stack.egg-info/dependency_links.txt +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/composable_data_stack.egg-info/entry_points.txt +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/composable_data_stack.egg-info/requires.txt +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/composable_data_stack.egg-info/top_level.txt +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/setup.cfg +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_cds_workflow.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_completion.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_compose_runtime_smoke.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_dagster_config_generation.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_dagster_definitions_uri.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_dagster_entrypoint_backend_guard.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_dagster_hardening.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_environment_classification.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_environment_cli.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_graph.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_image_security_scan_workflow.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_image_updates.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_image_verification.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_module_isolation.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_modules_no_committed_secrets.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_mvp_proof_workflow.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_overlay.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_planner.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_preflight.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_publish_images_workflow.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_python_version_alignment.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_release_version_check.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_render_example_profile.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_renderer.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_resolve_profile_path.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_resolver.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_secrets.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_security.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_smoke_example_profile.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_state.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_superset_hardening.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_trivyignore.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_up_runner.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_upstream_image_hardening.py +0 -0
- {composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_validator.py +0 -0
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import re
|
|
6
6
|
import shlex
|
|
7
7
|
import shutil
|
|
8
|
+
import sys
|
|
8
9
|
import tarfile
|
|
9
10
|
import tempfile
|
|
10
11
|
from collections.abc import Iterator
|
|
@@ -667,15 +668,49 @@ def _write_tracking_manifest(
|
|
|
667
668
|
def _read_tracking_manifest(path: Path) -> dict[str, Any]:
|
|
668
669
|
if not path.exists():
|
|
669
670
|
return {"version": 1, "profiles": {}}
|
|
671
|
+
|
|
670
672
|
try:
|
|
671
|
-
|
|
672
|
-
except
|
|
673
|
-
|
|
674
|
-
|
|
673
|
+
raw_text = path.read_text(encoding="utf-8")
|
|
674
|
+
except OSError as exc:
|
|
675
|
+
# The file could not be read at all, so it cannot be backed up either;
|
|
676
|
+
# skip the doomed copy attempt and report the read failure directly.
|
|
677
|
+
print(
|
|
678
|
+
f"WARNING {path} could not be read ({exc}); resetting tracking manifest.",
|
|
679
|
+
file=sys.stderr,
|
|
680
|
+
)
|
|
675
681
|
return {"version": 1, "profiles": {}}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
682
|
+
|
|
683
|
+
malformed_reason: str = ""
|
|
684
|
+
try:
|
|
685
|
+
data = json.loads(raw_text)
|
|
686
|
+
except json.JSONDecodeError as exc:
|
|
687
|
+
malformed_reason = f"invalid JSON: {exc}"
|
|
688
|
+
else:
|
|
689
|
+
if not isinstance(data, dict):
|
|
690
|
+
malformed_reason = "manifest root must be a JSON object"
|
|
691
|
+
else:
|
|
692
|
+
data.setdefault("version", 1)
|
|
693
|
+
data.setdefault("profiles", {})
|
|
694
|
+
return data
|
|
695
|
+
|
|
696
|
+
_backup_malformed_manifest(path, malformed_reason)
|
|
697
|
+
return {"version": 1, "profiles": {}}
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def _backup_malformed_manifest(path: Path, reason: str) -> None:
|
|
701
|
+
timestamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%S%fZ")
|
|
702
|
+
backup_path = path.with_name(f"{path.name}.corrupt-{timestamp}")
|
|
703
|
+
try:
|
|
704
|
+
shutil.copy2(path, backup_path)
|
|
705
|
+
backup_message = f"backed up to {backup_path}"
|
|
706
|
+
except OSError as exc:
|
|
707
|
+
backup_message = f"backup failed: {exc}"
|
|
708
|
+
|
|
709
|
+
print(
|
|
710
|
+
f"WARNING {path} is malformed ({reason}); resetting tracking manifest "
|
|
711
|
+
f"({backup_message}).",
|
|
712
|
+
file=sys.stderr,
|
|
713
|
+
)
|
|
679
714
|
|
|
680
715
|
|
|
681
716
|
def _asset_root_relative_path(asset_root: Path, source_repo: Path) -> str:
|
|
@@ -114,12 +114,19 @@ def get_modules_root() -> Path:
|
|
|
114
114
|
def find_project_root(start: Path | None = None) -> Path:
|
|
115
115
|
"""
|
|
116
116
|
Walk up from `start` (default: current working directory) looking for a
|
|
117
|
-
project root marker (
|
|
118
|
-
|
|
117
|
+
project root marker: `.cds` (CDS's own state directory, created by
|
|
118
|
+
`cds get`/`cds use`), `pyproject.toml`, or `.git`. `.cds` is checked first
|
|
119
|
+
at each level so a CDS working directory takes priority over an unrelated
|
|
120
|
+
ancestor repository (e.g. a dotfiles repo at $HOME) that happens to sit
|
|
121
|
+
further up the tree. Falls back to `start` itself if no marker is found.
|
|
119
122
|
"""
|
|
120
123
|
current = (start or Path.cwd()).resolve()
|
|
121
124
|
for directory in [current, *current.parents]:
|
|
122
|
-
if (
|
|
125
|
+
if (
|
|
126
|
+
(directory / ".cds").exists()
|
|
127
|
+
or (directory / "pyproject.toml").exists()
|
|
128
|
+
or (directory / ".git").exists()
|
|
129
|
+
):
|
|
123
130
|
return directory
|
|
124
131
|
return current
|
|
125
132
|
|
|
@@ -335,13 +342,19 @@ def resolve_project_root(profile_path: str) -> Path:
|
|
|
335
342
|
"""
|
|
336
343
|
Resolve a project root for output artifacts.
|
|
337
344
|
|
|
338
|
-
The resolver walks up from the selected profile location and picks the
|
|
339
|
-
directory containing
|
|
340
|
-
it
|
|
345
|
+
The resolver walks up from the selected profile location and picks the
|
|
346
|
+
first directory containing `.cds` (CDS's own state directory, checked
|
|
347
|
+
first so it takes priority over an unrelated ancestor repository),
|
|
348
|
+
`pyproject.toml`, or `.git`. If no marker is found, it falls back to the
|
|
349
|
+
current working directory.
|
|
341
350
|
"""
|
|
342
351
|
start = Path(profile_path).resolve().parent
|
|
343
352
|
for directory in [start, *start.parents]:
|
|
344
|
-
if (
|
|
353
|
+
if (
|
|
354
|
+
(directory / ".cds").exists()
|
|
355
|
+
or (directory / "pyproject.toml").exists()
|
|
356
|
+
or (directory / ".git").exists()
|
|
357
|
+
):
|
|
345
358
|
return directory
|
|
346
359
|
return Path.cwd().resolve()
|
|
347
360
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import io
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
@@ -119,6 +120,139 @@ class GetterTest(unittest.TestCase):
|
|
|
119
120
|
)
|
|
120
121
|
self.assertIn("kind: Profile", profile_file.read_text(encoding="utf-8"))
|
|
121
122
|
|
|
123
|
+
def test_fetch_profile_backs_up_and_warns_on_malformed_manifest(self) -> None:
|
|
124
|
+
with tempfile.TemporaryDirectory() as source_dir, tempfile.TemporaryDirectory() as dest_dir:
|
|
125
|
+
source_root = Path(source_dir)
|
|
126
|
+
destination_root = Path(dest_dir)
|
|
127
|
+
_make_source_repo(source_root)
|
|
128
|
+
|
|
129
|
+
manifest_path = destination_root / ".cds" / "get-manifest.json"
|
|
130
|
+
_write(manifest_path, "{not valid json")
|
|
131
|
+
|
|
132
|
+
stderr = io.StringIO()
|
|
133
|
+
with contextlib.redirect_stderr(stderr):
|
|
134
|
+
_, returned_manifest_path = fetch_profile(
|
|
135
|
+
"demo",
|
|
136
|
+
local=str(source_root),
|
|
137
|
+
destination_root=destination_root,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
self.assertEqual(returned_manifest_path.resolve(), manifest_path.resolve())
|
|
141
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
142
|
+
self.assertEqual(manifest["profiles"]["demo"]["requestedProfile"], "demo")
|
|
143
|
+
|
|
144
|
+
warning_output = stderr.getvalue()
|
|
145
|
+
self.assertIn("WARNING", warning_output)
|
|
146
|
+
self.assertIn("is malformed", warning_output)
|
|
147
|
+
self.assertIn("invalid JSON", warning_output)
|
|
148
|
+
|
|
149
|
+
backups = list(manifest_path.parent.glob("get-manifest.json.corrupt-*"))
|
|
150
|
+
self.assertEqual(len(backups), 1)
|
|
151
|
+
self.assertEqual(backups[0].read_text(encoding="utf-8"), "{not valid json")
|
|
152
|
+
self.assertIn(backups[0].name, warning_output)
|
|
153
|
+
|
|
154
|
+
def test_fetch_profile_backs_up_and_warns_on_non_dict_manifest(self) -> None:
|
|
155
|
+
with tempfile.TemporaryDirectory() as source_dir, tempfile.TemporaryDirectory() as dest_dir:
|
|
156
|
+
source_root = Path(source_dir)
|
|
157
|
+
destination_root = Path(dest_dir)
|
|
158
|
+
_make_source_repo(source_root)
|
|
159
|
+
|
|
160
|
+
manifest_path = destination_root / ".cds" / "get-manifest.json"
|
|
161
|
+
_write(manifest_path, "[1, 2, 3]")
|
|
162
|
+
|
|
163
|
+
stderr = io.StringIO()
|
|
164
|
+
with contextlib.redirect_stderr(stderr):
|
|
165
|
+
fetch_profile(
|
|
166
|
+
"demo",
|
|
167
|
+
local=str(source_root),
|
|
168
|
+
destination_root=destination_root,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
172
|
+
self.assertEqual(manifest["profiles"]["demo"]["requestedProfile"], "demo")
|
|
173
|
+
|
|
174
|
+
warning_output = stderr.getvalue()
|
|
175
|
+
self.assertIn("WARNING", warning_output)
|
|
176
|
+
self.assertIn("is malformed", warning_output)
|
|
177
|
+
self.assertIn("manifest root must be a JSON object", warning_output)
|
|
178
|
+
|
|
179
|
+
backups = list(manifest_path.parent.glob("get-manifest.json.corrupt-*"))
|
|
180
|
+
self.assertEqual(len(backups), 1)
|
|
181
|
+
self.assertIn(backups[0].name, warning_output)
|
|
182
|
+
|
|
183
|
+
def test_fetch_profile_warns_without_backup_when_manifest_unreadable(self) -> None:
|
|
184
|
+
with tempfile.TemporaryDirectory() as source_dir, tempfile.TemporaryDirectory() as dest_dir:
|
|
185
|
+
source_root = Path(source_dir)
|
|
186
|
+
destination_root = Path(dest_dir)
|
|
187
|
+
_make_source_repo(source_root)
|
|
188
|
+
|
|
189
|
+
manifest_path = destination_root / ".cds" / "get-manifest.json"
|
|
190
|
+
_write(manifest_path, "{}")
|
|
191
|
+
|
|
192
|
+
original_read_text = Path.read_text
|
|
193
|
+
|
|
194
|
+
def _raising_read_text(self: Path, *args: object, **kwargs: object) -> str:
|
|
195
|
+
if self.resolve() == manifest_path.resolve():
|
|
196
|
+
raise OSError("permission denied")
|
|
197
|
+
return original_read_text(self, *args, **kwargs)
|
|
198
|
+
|
|
199
|
+
stderr = io.StringIO()
|
|
200
|
+
with patch.object(Path, "read_text", _raising_read_text):
|
|
201
|
+
with contextlib.redirect_stderr(stderr):
|
|
202
|
+
fetch_profile(
|
|
203
|
+
"demo",
|
|
204
|
+
local=str(source_root),
|
|
205
|
+
destination_root=destination_root,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
209
|
+
self.assertEqual(manifest["profiles"]["demo"]["requestedProfile"], "demo")
|
|
210
|
+
|
|
211
|
+
warning_output = stderr.getvalue()
|
|
212
|
+
self.assertIn("WARNING", warning_output)
|
|
213
|
+
self.assertIn("could not be read", warning_output)
|
|
214
|
+
self.assertIn("permission denied", warning_output)
|
|
215
|
+
self.assertNotIn("is malformed", warning_output)
|
|
216
|
+
|
|
217
|
+
backups = list(manifest_path.parent.glob("get-manifest.json.corrupt-*"))
|
|
218
|
+
self.assertEqual(len(backups), 0)
|
|
219
|
+
|
|
220
|
+
def test_fetch_profile_loads_valid_manifest_unchanged(self) -> None:
|
|
221
|
+
with tempfile.TemporaryDirectory() as source_dir, tempfile.TemporaryDirectory() as dest_dir:
|
|
222
|
+
source_root = Path(source_dir)
|
|
223
|
+
destination_root = Path(dest_dir)
|
|
224
|
+
_make_source_repo(source_root)
|
|
225
|
+
|
|
226
|
+
manifest_path = destination_root / ".cds" / "get-manifest.json"
|
|
227
|
+
_write(
|
|
228
|
+
manifest_path,
|
|
229
|
+
json.dumps(
|
|
230
|
+
{
|
|
231
|
+
"version": 1,
|
|
232
|
+
"profiles": {
|
|
233
|
+
"other": {"requestedProfile": "other", "files": []},
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
stderr = io.StringIO()
|
|
240
|
+
with contextlib.redirect_stderr(stderr):
|
|
241
|
+
fetch_profile(
|
|
242
|
+
"demo",
|
|
243
|
+
local=str(source_root),
|
|
244
|
+
destination_root=destination_root,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
self.assertEqual(stderr.getvalue(), "")
|
|
248
|
+
|
|
249
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
250
|
+
self.assertIn("other", manifest["profiles"])
|
|
251
|
+
self.assertEqual(manifest["profiles"]["demo"]["requestedProfile"], "demo")
|
|
252
|
+
|
|
253
|
+
backups = list(manifest_path.parent.glob("get-manifest.json.corrupt-*"))
|
|
254
|
+
self.assertEqual(len(backups), 0)
|
|
255
|
+
|
|
122
256
|
def test_fetch_profile_dry_run_ignores_conflicting_files(self) -> None:
|
|
123
257
|
with tempfile.TemporaryDirectory() as source_dir, tempfile.TemporaryDirectory() as dest_dir:
|
|
124
258
|
source_root = Path(source_dir)
|
|
@@ -395,6 +395,60 @@ class MainCLITest(unittest.TestCase):
|
|
|
395
395
|
|
|
396
396
|
self.assertEqual(resolved, Path("/mock/cwd").resolve())
|
|
397
397
|
|
|
398
|
+
def test_find_project_root_prefers_nearby_cds_over_ancestor_git(self):
|
|
399
|
+
"""Regression test: a `cds get`/`cds use` working directory that has
|
|
400
|
+
its own `.cds` marker must not be shadowed by an unrelated `.git`
|
|
401
|
+
repository further up the tree (e.g. a dotfiles repo at $HOME)."""
|
|
402
|
+
import tempfile
|
|
403
|
+
from cli.main import find_project_root
|
|
404
|
+
|
|
405
|
+
with tempfile.TemporaryDirectory() as td:
|
|
406
|
+
home_like_root = Path(td)
|
|
407
|
+
(home_like_root / ".git").mkdir()
|
|
408
|
+
work_dir = home_like_root / "tmp"
|
|
409
|
+
work_dir.mkdir()
|
|
410
|
+
(work_dir / ".cds").mkdir()
|
|
411
|
+
|
|
412
|
+
resolved = find_project_root(work_dir)
|
|
413
|
+
|
|
414
|
+
self.assertEqual(resolved, work_dir.resolve())
|
|
415
|
+
|
|
416
|
+
def test_find_project_root_falls_back_to_git_when_no_cds_marker(self):
|
|
417
|
+
"""A fresh checkout of the CDS repo itself (no `.cds` yet) should
|
|
418
|
+
still resolve to the repo root via `.git`/`pyproject.toml`."""
|
|
419
|
+
import tempfile
|
|
420
|
+
from cli.main import find_project_root
|
|
421
|
+
|
|
422
|
+
with tempfile.TemporaryDirectory() as td:
|
|
423
|
+
repo_root = Path(td)
|
|
424
|
+
(repo_root / ".git").mkdir()
|
|
425
|
+
sub_dir = repo_root / "profiles" / "some-profile"
|
|
426
|
+
sub_dir.mkdir(parents=True)
|
|
427
|
+
|
|
428
|
+
resolved = find_project_root(sub_dir)
|
|
429
|
+
|
|
430
|
+
self.assertEqual(resolved, repo_root.resolve())
|
|
431
|
+
|
|
432
|
+
def test_resolve_project_root_prefers_nearby_cds_over_ancestor_git(self):
|
|
433
|
+
"""Same regression as find_project_root, but for the resolver used
|
|
434
|
+
to place rendered docker-compose.yml output for `cds up`/`render`."""
|
|
435
|
+
import tempfile
|
|
436
|
+
from cli.main import resolve_project_root
|
|
437
|
+
|
|
438
|
+
with tempfile.TemporaryDirectory() as td:
|
|
439
|
+
home_like_root = Path(td)
|
|
440
|
+
(home_like_root / ".git").mkdir()
|
|
441
|
+
work_dir = home_like_root / "tmp"
|
|
442
|
+
work_dir.mkdir()
|
|
443
|
+
(work_dir / ".cds").mkdir()
|
|
444
|
+
profile_path = work_dir / "profiles" / "some-profile" / "profile.yaml"
|
|
445
|
+
profile_path.parent.mkdir(parents=True)
|
|
446
|
+
profile_path.touch()
|
|
447
|
+
|
|
448
|
+
resolved = resolve_project_root(str(profile_path))
|
|
449
|
+
|
|
450
|
+
self.assertEqual(resolved, work_dir.resolve())
|
|
451
|
+
|
|
398
452
|
def test_init_generates_env_file_from_profile_secrets(self):
|
|
399
453
|
import tempfile
|
|
400
454
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/contract.schema.json
RENAMED
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/module.schema.json
RENAMED
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/cli/resources/profile.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_compose_runtime_smoke.py
RENAMED
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_dagster_config_generation.py
RENAMED
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_dagster_definitions_uri.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_environment_classification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_image_verification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_mvp_proof_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_publish_images_workflow.py
RENAMED
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_python_version_alignment.py
RENAMED
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_release_version_check.py
RENAMED
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_render_example_profile.py
RENAMED
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_resolve_profile_path.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_smoke_example_profile.py
RENAMED
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_superset_hardening.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{composable_data_stack-0.5.1 → composable_data_stack-0.5.2}/tests/test_upstream_image_hardening.py
RENAMED
|
File without changes
|
|
File without changes
|