datatrail-cli 1.0.2__tar.gz → 1.1.0__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.
Files changed (79) hide show
  1. datatrail_cli-1.1.0/.github/dependabot.yml +12 -0
  2. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.github/workflows/continuous-deployment.yml +2 -1
  3. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.github/workflows/continuous-integration.yml +2 -2
  4. datatrail_cli-1.1.0/.github/workflows/docs.yml +46 -0
  5. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.github/workflows/stale.yml +2 -1
  6. datatrail_cli-1.1.0/.release-please-manifest.json +3 -0
  7. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/CHANGELOG.md +7 -0
  8. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/PKG-INFO +1 -1
  9. datatrail_cli-1.1.0/docs/workflow.md +52 -0
  10. datatrail_cli-1.1.0/dtcli/workflow.py +170 -0
  11. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/mkdocs.yml +1 -0
  12. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/pyproject.toml +1 -1
  13. datatrail_cli-1.1.0/tests/test_workflow.py +189 -0
  14. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/uv.lock +1 -1
  15. datatrail_cli-1.0.2/.github/workflows/docs.yml +0 -23
  16. datatrail_cli-1.0.2/.release-please-manifest.json +0 -3
  17. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  18. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  19. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  20. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.github/workflows/codeql.yml +0 -0
  21. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.gitignore +0 -0
  22. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/.pre-commit-config.yaml +0 -0
  23. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/LICENSE +0 -0
  24. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/README.md +0 -0
  25. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/clear.md +0 -0
  26. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/cli.md +0 -0
  27. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/commands.md +0 -0
  28. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/discovery.md +0 -0
  29. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/doctor.md +0 -0
  30. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/images/Datatrail-logo.png +0 -0
  31. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/index.md +0 -0
  32. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/initialising.md +0 -0
  33. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/install.md +0 -0
  34. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/inventory.md +0 -0
  35. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/list.md +0 -0
  36. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/ps.md +0 -0
  37. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/pull-manifest.md +0 -0
  38. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/pull.md +0 -0
  39. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/scout.md +0 -0
  40. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/unregistered.md +0 -0
  41. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/user_guide.md +0 -0
  42. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/docs/verify.md +0 -0
  43. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/__init__.py +0 -0
  44. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/clear.py +0 -0
  45. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/cli.py +0 -0
  46. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/config.py +0 -0
  47. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/config.yaml +0 -0
  48. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/doctor.py +0 -0
  49. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/inventory.py +0 -0
  50. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/locking.py +0 -0
  51. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/ls.py +0 -0
  52. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/ps.py +0 -0
  53. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/pull.py +0 -0
  54. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/pull_manifest.py +0 -0
  55. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/scout.py +0 -0
  56. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/src/functions.py +0 -0
  57. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/unregistered.py +0 -0
  58. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/utilities/cadcclient.py +0 -0
  59. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/utilities/results.py +0 -0
  60. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/utilities/utilities.py +0 -0
  61. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/dtcli/verify.py +0 -0
  62. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/mkdocs-site.yml +0 -0
  63. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/release-please-config.json +0 -0
  64. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/conftest.py +0 -0
  65. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_cadc_summary.py +0 -0
  66. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_canfar_permissions.py +0 -0
  67. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_cli.py +0 -0
  68. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_config.py +0 -0
  69. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_doctor.py +0 -0
  70. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_functions.py +0 -0
  71. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_inventory.py +0 -0
  72. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_locking.py +0 -0
  73. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_pull_manifest.py +0 -0
  74. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_pull_reliability.py +0 -0
  75. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_pull_size.py +0 -0
  76. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_scout.py +0 -0
  77. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_utils.py +0 -0
  78. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tests/test_verify.py +0 -0
  79. {datatrail_cli-1.0.2 → datatrail_cli-1.1.0}/tox.ini +0 -0
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ open-pull-requests-limit: 5
9
+ groups:
10
+ actions-minor-and-patch:
11
+ patterns: ["*"]
12
+ update-types: [minor, patch]
@@ -9,6 +9,7 @@ permissions:
9
9
  pull-requests: write
10
10
  jobs:
11
11
  release-please:
12
+ if: github.repository == 'CHIMEFRB/datatrail-cli'
12
13
  runs-on: ubuntu-latest
13
14
  steps:
14
15
  - name: Create release
@@ -23,7 +24,7 @@ jobs:
23
24
 
24
25
  - name: Install uv
25
26
  if: ${{ steps.release-please.outputs.release_created }}
26
- uses: astral-sh/setup-uv@v10.0.1
27
+ uses: astral-sh/setup-uv@v10.2.0
27
28
  with:
28
29
  enable-cache: false
29
30
  python-version: "3.13"
@@ -49,7 +49,7 @@ jobs:
49
49
  - uses: actions/checkout@v7
50
50
 
51
51
  - name: Install uv
52
- uses: astral-sh/setup-uv@v10.0.1
52
+ uses: astral-sh/setup-uv@v10.2.0
53
53
  with:
54
54
  enable-cache: true
55
55
  cache-dependency-glob: "uv.lock"
@@ -100,7 +100,7 @@ jobs:
100
100
  fail-on-error: false
101
101
 
102
102
  - name: Upload coverage to Codecov
103
- uses: codecov/codecov-action@v3
103
+ uses: codecov/codecov-action@v7.1.1
104
104
  with:
105
105
  fail_ci_if_error: false
106
106
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,46 @@
1
+ name: Documentation
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ paths:
6
+ - "docs/**"
7
+ - "mkdocs*.yml"
8
+ - "pyproject.toml"
9
+ - "uv.lock"
10
+ - ".github/workflows/docs.yml"
11
+ pull_request:
12
+ paths:
13
+ - "docs/**"
14
+ - "mkdocs*.yml"
15
+ - "pyproject.toml"
16
+ - "uv.lock"
17
+ - ".github/workflows/docs.yml"
18
+ workflow_dispatch:
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ jobs:
24
+ build:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v7
28
+ - uses: astral-sh/setup-uv@v10.2.0
29
+ with:
30
+ python-version: "3.13"
31
+ enable-cache: true
32
+ - run: uv sync --locked --group docs
33
+ - run: uv run --no-sync mkdocs build --strict --config-file mkdocs-site.yml
34
+
35
+ handbook:
36
+ if: github.repository == 'CHIMEFRB/datatrail-cli' && github.event_name == 'push'
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - name: Build the upstream handbook
41
+ uses: peter-evans/repository-dispatch@v4.0.1
42
+ with:
43
+ token: ${{ secrets.BUILD_HANDBOOK_TOKEN }}
44
+ repository: chimefrb/handbook
45
+ event-type: build-handbook
46
+ client-payload: '{"source": "chimefrb/datatrail-cli"}'
@@ -11,10 +11,11 @@ permissions:
11
11
 
12
12
  jobs:
13
13
  close_stale_prs:
14
+ if: github.repository == 'CHIMEFRB/datatrail-cli'
14
15
  runs-on: ubuntu-latest
15
16
  steps:
16
17
  - name: Close stale pull requests
17
- uses: actions/stale@v9
18
+ uses: actions/stale@v11.0.0
18
19
  with:
19
20
  days-before-stale: 90
20
21
  days-before-close: 7
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.1.0"
3
+ }
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.0](https://github.com/CHIMEFRB/datatrail-cli/compare/datatrail-cli-v1.0.2...datatrail-cli-v1.1.0) (2026-11-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * **workflow:** pull and clear as functions for Workflow pipelines ([de80e6d](https://github.com/CHIMEFRB/datatrail-cli/commit/de80e6dfd6f6092457af038002b0dec784a74cf5))
9
+
3
10
  ## [1.0.2](https://github.com/CHIMEFRB/datatrail-cli/compare/datatrail-cli-v1.0.1...datatrail-cli-v1.0.2) (2026-09-29)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datatrail-cli
3
- Version: 1.0.2
3
+ Version: 1.1.0
4
4
  Summary: CHIME/FRB Datatrail CLI
5
5
  Author: CHIME FRB Project Office
6
6
  Author-email: Tarik Zegmott <tarik.zegmott@mcgill.ca>
@@ -0,0 +1,52 @@
1
+ # 🔁 In Workflow pipelines
2
+
3
+ `dtcli.workflow` has `pull` and `clear` as Python functions for Workflow pipeline
4
+ steps. A function runs in the step's own session, so what the command prints is in
5
+ the step's logs. Each one checks what the command did and raises if it failed, so
6
+ the step fails too: `datatrail pull` reports some failures (no CADC certificate,
7
+ minoc down, no files found) only on the console.
8
+
9
+ They return `(results, products, plots)`, as Workflow expects of a function.
10
+
11
+ ## `pull_dataset`
12
+
13
+ Downloads a dataset, then checks that all its files are here.
14
+
15
+ ```yaml
16
+ work:
17
+ function: dtcli.workflow.pull_dataset
18
+ parameters:
19
+ scope: chime.event.baseband.raw
20
+ dataset: "123456789"
21
+ cores: 4
22
+ site: canfar
23
+ ```
24
+
25
+ - `cores`: parallel fetch processes (default 1).
26
+ - `site`: where it runs. If there's no Datatrail configuration, one is created
27
+ for this site (as `datatrail config init --site <site>`); if there is one for
28
+ another site, the step fails.
29
+ - `verbose`: 1 for INFO (default), 2 for DEBUG.
30
+
31
+ Results: `scope`, `dataset`, `files` (how many are here) and `directory`.
32
+
33
+ ## `clear_dataset`
34
+
35
+ Deletes a dataset's local copy, then checks that it's gone. A dataset with no local
36
+ copy is left as it is, and the step succeeds, so it can clean up after a pull that
37
+ failed.
38
+
39
+ ```yaml
40
+ work:
41
+ function: dtcli.workflow.clear_dataset
42
+ parameters:
43
+ scope: chime.event.baseband.raw
44
+ dataset: "123456789"
45
+ site: canfar
46
+ ```
47
+
48
+ - `clear_parents`: also delete empty parent directories (default false).
49
+ - `site` and `verbose` as for `pull_dataset`.
50
+
51
+ Results: `scope`, `dataset`, `path` and `cleared` (false if there was nothing to
52
+ clear).
@@ -0,0 +1,170 @@
1
+ """Datatrail commands as functions for Workflow pipelines.
2
+
3
+ Workflow runs a pipeline step's function in the session's own process, so what the
4
+ command prints is the step's logs. Each function runs the CLI command, then checks
5
+ what it did and raises if it failed: the commands report some failures only on the
6
+ console, which a Workflow step can't see. They return (results, products, plots),
7
+ the shape Workflow expects of a function.
8
+
9
+ Example step:
10
+
11
+ function: dtcli.workflow.pull_dataset
12
+ parameters:
13
+ scope: chime.event.baseband.raw
14
+ dataset: "123456789"
15
+ cores: 4
16
+ site: canfar
17
+ """
18
+
19
+ import logging
20
+ import os
21
+ from typing import Any, Dict, List, Optional, Tuple
22
+
23
+ import click
24
+
25
+ from dtcli.clear import clear
26
+ from dtcli.config import CONFIG, init, procure
27
+ from dtcli.pull import pull
28
+ from dtcli.src.functions import find_dataset_common_path, find_missing_dataset_files
29
+
30
+ logger = logging.getLogger("workflow")
31
+
32
+ Outcome = Tuple[Dict[str, Any], List[str], List[str]]
33
+
34
+
35
+ def _configure(site: Optional[str]) -> Dict[str, Any]:
36
+ """The Datatrail configuration, created for `site` if there's none.
37
+
38
+ Raises:
39
+ RuntimeError: If there's no configuration and no site to create one for,
40
+ or it's set up for another site.
41
+ """
42
+ if not CONFIG.exists():
43
+ if not site:
44
+ raise RuntimeError(
45
+ f"There's no Datatrail configuration at {CONFIG}; "
46
+ "give a site to create one."
47
+ )
48
+ # init only asks questions when a configuration exists
49
+ init.callback(site=site)
50
+ config = procure()
51
+ if not config:
52
+ raise RuntimeError(f"The Datatrail configuration at {CONFIG} can't be read.")
53
+ if site and config.get("site") != site:
54
+ raise RuntimeError(
55
+ f"The Datatrail configuration at {CONFIG} is for {config.get('site')}, "
56
+ f"not {site}."
57
+ )
58
+ return config
59
+
60
+
61
+ def _run(command: click.Command, args: List[str]) -> None:
62
+ """Run a CLI command in this process.
63
+
64
+ Raises:
65
+ RuntimeError: If it exits with an error code.
66
+ """
67
+ code = command.main(args=args, standalone_mode=False)
68
+ # with standalone_mode=False, an exit code is returned rather than raised
69
+ if code:
70
+ raise RuntimeError(f"datatrail {command.name} exited with {code}.")
71
+
72
+
73
+ def pull_dataset(
74
+ scope: str,
75
+ dataset: str,
76
+ cores: int = 1,
77
+ site: Optional[str] = None,
78
+ verbose: int = 1,
79
+ ) -> Outcome:
80
+ """Download a dataset, then check that all its files are here.
81
+
82
+ Args:
83
+ scope (str): Scope of the dataset.
84
+ dataset (str): Name of the dataset.
85
+ cores (int): Number of parallel fetch processes. Defaults to 1.
86
+ site (Optional[str]): Site this runs at; the configuration is created for it
87
+ if there's none. Defaults to the configured site.
88
+ verbose (int): Verbosity: 1=INFO, 2=DEBUG. Defaults to 1.
89
+
90
+ Returns:
91
+ Outcome: Results (scope, dataset, number of files and where they are), no
92
+ products and no plots.
93
+
94
+ Raises:
95
+ RuntimeError: If the dataset can't be found, or any of its files is missing
96
+ after the pull.
97
+ """
98
+ config = _configure(site)
99
+ directory = config["root_mounts"][config["site"]]
100
+ dataset = str(dataset)
101
+ args = [scope, dataset, "--force", "--cores", str(cores)] + ["-v"] * verbose
102
+ _run(pull, args)
103
+
104
+ files = find_missing_dataset_files(scope, dataset, directory, verbose)
105
+ if files.get("error"):
106
+ raise RuntimeError(f"Could not check {scope} {dataset}: {files['error']}")
107
+ if files["missing"]:
108
+ raise RuntimeError(
109
+ f"{len(files['missing'])} of {scope} {dataset}'s files weren't pulled, "
110
+ f"e.g. {files['missing'][0]}."
111
+ )
112
+ if not files["existing"]:
113
+ raise RuntimeError(f"Datatrail has no files for {scope} {dataset}.")
114
+ results = {
115
+ "scope": scope,
116
+ "dataset": dataset,
117
+ "files": len(files["existing"]),
118
+ "directory": directory,
119
+ }
120
+ return results, [], []
121
+
122
+
123
+ def clear_dataset(
124
+ scope: str,
125
+ dataset: str,
126
+ site: Optional[str] = None,
127
+ clear_parents: bool = False,
128
+ verbose: int = 1,
129
+ ) -> Outcome:
130
+ """Delete a dataset's local copy, then check that it's gone.
131
+
132
+ A dataset with no local copy is left as it is, and isn't a failure.
133
+
134
+ Args:
135
+ scope (str): Scope of the dataset.
136
+ dataset (str): Name of the dataset.
137
+ site (Optional[str]): Site this runs at; the configuration is created for it
138
+ if there's none. Defaults to the configured site.
139
+ clear_parents (bool): Also delete empty parent directories. Defaults to False.
140
+ verbose (int): Verbosity: 1=INFO, 2=DEBUG. Defaults to 1.
141
+
142
+ Returns:
143
+ Outcome: Results (scope, dataset, the path and whether it was cleared), no
144
+ products and no plots.
145
+
146
+ Raises:
147
+ RuntimeError: If the dataset can't be found, or its files are still there.
148
+ """
149
+ config = _configure(site)
150
+ directory = config["root_mounts"][config["site"]]
151
+ dataset = str(dataset)
152
+ common = find_dataset_common_path(scope, dataset, config["site"], verbose, False)
153
+ # it returns a message rather than a path when the server isn't reachable
154
+ if not common or not common.startswith(("data/", "/")):
155
+ raise RuntimeError(f"Could not find where {scope} {dataset} is: {common}")
156
+ # as `datatrail clear` builds it
157
+ path = (directory + common).replace("//", "/")
158
+ results: Dict[str, Any] = {"scope": scope, "dataset": dataset, "path": path}
159
+ if not os.path.exists(path):
160
+ logger.info(f"{path} doesn't exist; nothing to clear.")
161
+ return {**results, "cleared": False}, [], []
162
+
163
+ args = [scope, dataset, "--force"] + ["-v"] * verbose
164
+ if clear_parents:
165
+ args.append("--clear-parents")
166
+ _run(clear, args)
167
+
168
+ if os.path.exists(path):
169
+ raise RuntimeError(f"{path} is still there after clearing it.")
170
+ return {**results, "cleared": True}, [], []
@@ -20,6 +20,7 @@ nav:
20
20
  - scout: scout.md
21
21
  - unregistered: unregistered.md
22
22
  - verify: verify.md
23
+ - In Workflow pipelines: workflow.md
23
24
  - Command Line Interface:
24
25
  - Commands: commands.md
25
26
  - Reference: cli.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datatrail-cli"
3
- version = "1.0.2"
3
+ version = "1.1.0"
4
4
  description = "CHIME/FRB Datatrail CLI"
5
5
  authors = [
6
6
  { name = "CHIME FRB Project Office" },
@@ -0,0 +1,189 @@
1
+ """Tests for the Datatrail functions for Workflow pipelines."""
2
+
3
+ from types import SimpleNamespace
4
+
5
+ import click
6
+ import pytest
7
+ import yaml
8
+
9
+ from dtcli import workflow
10
+
11
+
12
+ class FakeCommand:
13
+ """A CLI command that records its arguments and returns an exit code."""
14
+
15
+ def __init__(self, name, code=None, effect=None):
16
+ self.name = name
17
+ self.code = code
18
+ self.effect = effect
19
+ self.calls = []
20
+
21
+ def main(self, args, standalone_mode):
22
+ assert standalone_mode is False
23
+ self.calls.append(args)
24
+ if self.effect:
25
+ self.effect()
26
+ return self.code
27
+
28
+
29
+ @pytest.fixture
30
+ def config(tmp_path, monkeypatch):
31
+ """A Datatrail configuration for canfar, with its root mount in tmp_path."""
32
+ path = tmp_path / ".datatrail" / "config.yaml"
33
+ root = str(tmp_path / "arc") + "/"
34
+ monkeypatch.setattr(workflow, "CONFIG", path)
35
+
36
+ def write(site="canfar"):
37
+ path.parent.mkdir(parents=True, exist_ok=True)
38
+ path.write_text(yaml.safe_dump({"site": site, "root_mounts": {site: root}}))
39
+
40
+ monkeypatch.setattr(
41
+ workflow, "init", SimpleNamespace(callback=lambda site: write(site))
42
+ )
43
+ monkeypatch.setattr(workflow, "procure", lambda: yaml.safe_load(path.read_text()))
44
+ return SimpleNamespace(path=path, root=root, write=write)
45
+
46
+
47
+ def test_pull_dataset_returns_workflow_outcome(config, monkeypatch):
48
+ config.write()
49
+ pull = FakeCommand("pull")
50
+ monkeypatch.setattr(workflow, "pull", pull)
51
+ monkeypatch.setattr(
52
+ workflow,
53
+ "find_missing_dataset_files",
54
+ lambda scope, dataset, root, verbose: {"missing": [], "existing": ["a", "b"]},
55
+ )
56
+
57
+ results, products, plots = workflow.pull_dataset(
58
+ "chime.event.baseband.raw", 123, cores=4, site="canfar"
59
+ )
60
+
61
+ assert pull.calls == [
62
+ ["chime.event.baseband.raw", "123", "--force", "--cores", "4", "-v"]
63
+ ]
64
+ assert results == {
65
+ "scope": "chime.event.baseband.raw",
66
+ "dataset": "123",
67
+ "files": 2,
68
+ "directory": config.root,
69
+ }
70
+ assert products == [] and plots == []
71
+
72
+
73
+ def test_pull_dataset_creates_the_configuration(config, monkeypatch):
74
+ monkeypatch.setattr(workflow, "pull", FakeCommand("pull"))
75
+ monkeypatch.setattr(
76
+ workflow,
77
+ "find_missing_dataset_files",
78
+ lambda *args: {"missing": [], "existing": ["a"]},
79
+ )
80
+ workflow.pull_dataset("scope", "1", site="canfar")
81
+ assert yaml.safe_load(config.path.read_text())["site"] == "canfar"
82
+
83
+
84
+ def test_pull_dataset_needs_a_site_without_configuration(config):
85
+ with pytest.raises(RuntimeError, match="give a site"):
86
+ workflow.pull_dataset("scope", "1")
87
+
88
+
89
+ def test_pull_dataset_refuses_another_sites_configuration(config):
90
+ config.write("chime")
91
+ with pytest.raises(RuntimeError, match="is for chime, not canfar"):
92
+ workflow.pull_dataset("scope", "1", site="canfar")
93
+
94
+
95
+ @pytest.mark.parametrize(
96
+ "code, files, message",
97
+ [
98
+ # ctx.exit(1) comes back as a return value
99
+ (1, {"missing": [], "existing": ["a"]}, "exited with 1"),
100
+ # pull reports these only on the console and exits 0
101
+ (None, {"error": "Scope does not exist"}, "Could not check"),
102
+ (None, {"missing": ["a"], "existing": ["b"]}, "1 of scope 1's files"),
103
+ (None, {"missing": [], "existing": []}, "has no files"),
104
+ ],
105
+ )
106
+ def test_pull_dataset_raises_when_it_failed(config, monkeypatch, code, files, message):
107
+ config.write()
108
+ monkeypatch.setattr(workflow, "pull", FakeCommand("pull", code=code))
109
+ monkeypatch.setattr(workflow, "find_missing_dataset_files", lambda *args: files)
110
+ with pytest.raises(RuntimeError, match=message):
111
+ workflow.pull_dataset("scope", "1", site="canfar")
112
+
113
+
114
+ def test_pull_dataset_passes_on_click_errors(config, monkeypatch):
115
+ config.write()
116
+
117
+ def abort():
118
+ raise click.Abort()
119
+
120
+ monkeypatch.setattr(workflow, "pull", FakeCommand("pull", effect=abort))
121
+ with pytest.raises(click.Abort):
122
+ workflow.pull_dataset("scope", "1", site="canfar")
123
+
124
+
125
+ def _dataset_dir(config):
126
+ path = config.path.parent.parent / "arc" / "data" / "chime" / "astro_1"
127
+ path.mkdir(parents=True)
128
+ (path / "baseband_1_0.h5").write_text("")
129
+ return path
130
+
131
+
132
+ def test_clear_dataset_clears_and_checks(config, monkeypatch):
133
+ config.write()
134
+ path = _dataset_dir(config)
135
+
136
+ def delete():
137
+ for file in path.iterdir():
138
+ file.unlink()
139
+ path.rmdir()
140
+
141
+ clear = FakeCommand("clear", effect=delete)
142
+ monkeypatch.setattr(workflow, "clear", clear)
143
+ monkeypatch.setattr(
144
+ workflow, "find_dataset_common_path", lambda *args: "data/chime/astro_1/"
145
+ )
146
+
147
+ results, products, plots = workflow.clear_dataset("scope", 1, site="canfar")
148
+
149
+ assert clear.calls == [["scope", "1", "--force", "-v"]]
150
+ assert results == {
151
+ "scope": "scope",
152
+ "dataset": "1",
153
+ "path": config.root + "data/chime/astro_1/",
154
+ "cleared": True,
155
+ }
156
+ assert products == [] and plots == []
157
+
158
+
159
+ def test_clear_dataset_without_a_local_copy(config, monkeypatch):
160
+ config.write()
161
+ clear = FakeCommand("clear")
162
+ monkeypatch.setattr(workflow, "clear", clear)
163
+ monkeypatch.setattr(
164
+ workflow, "find_dataset_common_path", lambda *args: "data/chime/astro_2/"
165
+ )
166
+ results, _, _ = workflow.clear_dataset("scope", "2", site="canfar")
167
+ assert results["cleared"] is False
168
+ assert clear.calls == []
169
+
170
+
171
+ def test_clear_dataset_raises_when_files_remain(config, monkeypatch):
172
+ config.write()
173
+ _dataset_dir(config)
174
+ monkeypatch.setattr(workflow, "clear", FakeCommand("clear"))
175
+ monkeypatch.setattr(
176
+ workflow, "find_dataset_common_path", lambda *args: "data/chime/astro_1/"
177
+ )
178
+ with pytest.raises(RuntimeError, match="still there"):
179
+ workflow.clear_dataset("scope", "1", site="canfar")
180
+
181
+
182
+ @pytest.mark.parametrize(
183
+ "common", [None, "The Datatrail Central Server at CHIME at is not reachable!!!"]
184
+ )
185
+ def test_clear_dataset_raises_when_it_cant_find_the_dataset(config, monkeypatch, common):
186
+ config.write()
187
+ monkeypatch.setattr(workflow, "find_dataset_common_path", lambda *args: common)
188
+ with pytest.raises(RuntimeError, match="Could not find where"):
189
+ workflow.clear_dataset("scope", "1", site="canfar")
@@ -508,7 +508,7 @@ wheels = [
508
508
 
509
509
  [[package]]
510
510
  name = "datatrail-cli"
511
- version = "1.0.2"
511
+ version = "1.1.0"
512
512
  source = { editable = "." }
513
513
  dependencies = [
514
514
  { name = "cadcdata" },
@@ -1,23 +0,0 @@
1
- name: Documentation
2
- on:
3
- push:
4
- branches:
5
- - main
6
- paths:
7
- - "docs/**"
8
- - ".github/workflows/docs.yml"
9
-
10
- permissions:
11
- contents: read
12
-
13
- jobs:
14
- mkdocs:
15
- runs-on: ubuntu-latest
16
- steps:
17
- - name: Build chimefrb/handbook
18
- uses: peter-evans/repository-dispatch@v3
19
- with:
20
- token: ${{ secrets.BUILD_HANDBOOK_TOKEN }}
21
- repository: chimefrb/handbook
22
- event-type: build-handbook
23
- client-payload: '{"source": "chimefrb/datatrail-cli"}'
@@ -1,3 +0,0 @@
1
- {
2
- ".": "1.0.2"
3
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes