dkist-processing-core 7.2.1__tar.gz → 7.2.2rc1__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 (58) hide show
  1. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/PKG-INFO +3 -1
  2. dkist_processing_core-7.2.2rc1/changelog/72.misc.rst +1 -0
  3. dkist_processing_core-7.2.2rc1/changelog/76.feature.1.rst +1 -0
  4. dkist_processing_core-7.2.2rc1/changelog/76.feature.2.rst +1 -0
  5. dkist_processing_core-7.2.2rc1/changelog/77.misc.rst +1 -0
  6. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/build_utils.py +1 -0
  7. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/config.py +5 -0
  8. dkist_processing_core-7.2.2rc1/dkist_processing_core/notebook_utils.py +75 -0
  9. dkist_processing_core-7.2.2rc1/dkist_processing_core/tests/test_notebook_utils.py +90 -0
  10. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/test_workflow.py +3 -2
  11. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/workflow.py +30 -27
  12. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core.egg-info/PKG-INFO +3 -1
  13. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core.egg-info/SOURCES.txt +6 -0
  14. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core.egg-info/requires.txt +2 -0
  15. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/pyproject.toml +2 -0
  16. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/.gitignore +0 -0
  17. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/.pre-commit-config.yaml +0 -0
  18. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/.readthedocs.yml +0 -0
  19. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/.snyk +0 -0
  20. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/CHANGELOG.rst +0 -0
  21. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/README.rst +0 -0
  22. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/bitbucket-pipelines.yml +0 -0
  23. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/changelog/.gitempty +0 -0
  24. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/__init__.py +0 -0
  25. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/failure_callback.py +0 -0
  26. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/node.py +0 -0
  27. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/resource_queue.py +0 -0
  28. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/task.py +0 -0
  29. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/__init__.py +0 -0
  30. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/conftest.py +0 -0
  31. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/invalid_workflow_cyclic/__init__.py +0 -0
  32. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/invalid_workflow_cyclic/workflow.py +0 -0
  33. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/invalid_workflow_for_docker_multi_category/__init__.py +0 -0
  34. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/invalid_workflow_for_docker_multi_category/workflow.py +0 -0
  35. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/task_example.py +0 -0
  36. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/test_build_utils.py +0 -0
  37. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/test_export.py +0 -0
  38. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/test_failure_callback.py +0 -0
  39. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/test_node.py +0 -0
  40. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/test_task.py +0 -0
  41. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/valid_workflow_package/__init__.py +0 -0
  42. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/valid_workflow_package/workflow.py +0 -0
  43. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/zero_node_workflow_package/__init__.py +0 -0
  44. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core/tests/zero_node_workflow_package/workflow.py +0 -0
  45. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core.egg-info/dependency_links.txt +0 -0
  46. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/dkist_processing_core.egg-info/top_level.txt +0 -0
  47. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/Makefile +0 -0
  48. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/auto-proc-concept-model.png +0 -0
  49. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/auto_proc_brick.png +0 -0
  50. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/automated-processing-deployed.png +0 -0
  51. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/changelog.rst +0 -0
  52. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/conf.py +0 -0
  53. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/index.rst +0 -0
  54. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/landing_page.rst +0 -0
  55. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/make.bat +0 -0
  56. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/docs/requirements.txt +0 -0
  57. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/licenses/LICENSE.rst +0 -0
  58. {dkist_processing_core-7.2.1 → dkist_processing_core-7.2.2rc1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-core
3
- Version: 7.2.1
3
+ Version: 7.2.2rc1
4
4
  Summary: Abstraction layer used by the DKIST science data processing pipelines with Apache Airflow
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause
@@ -18,6 +18,8 @@ Requires-Dist: requests>=2.23
18
18
  Requires-Dist: talus<2.0,>=1.3.4
19
19
  Requires-Dist: pendulum
20
20
  Requires-Dist: nbformat>=5.9.2
21
+ Requires-Dist: notebook<8.0,>=7.5.5
22
+ Requires-Dist: nbconvert<8.0,>=7.16.6
21
23
  Requires-Dist: dkist-service-configuration<5.0,>=4.3.0
22
24
  Requires-Dist: pydantic>2.0
23
25
  Provides-Extra: test
@@ -0,0 +1 @@
1
+ Add an environment variable to indicate when the execution environment is a Jupyter notebook.
@@ -0,0 +1 @@
1
+ Add utility for working with the notebook build target which supports waiting for a notebook to autosave.
@@ -0,0 +1 @@
1
+ Add utility for working with the notebook build target which supports exporting a notebook as markdown.
@@ -0,0 +1 @@
1
+ Refactor the dag naming function to be accessible outside of the context of a Workflow instance.
@@ -115,6 +115,7 @@ class NotebookDockerfile:
115
115
  def setup(self) -> list[str]:
116
116
  """Environment setup lines."""
117
117
  return [
118
+ "ENV IS_NOTEBOOK_EXECUTION_ENVIRONMENT=true",
118
119
  "COPY . /app",
119
120
  "WORKDIR /app",
120
121
  "RUN python -m pip install -U pip",
@@ -27,6 +27,11 @@ class DKISTProcessingCoreConfiguration(InstrumentedMeshServiceConfigurationBase)
27
27
  )
28
28
  pip_timeout: int = Field(default=15, description="Timeout for pip installs in seconds.")
29
29
  pip_retries: int = Field(default=5, description="Number of retries for pip installs.")
30
+ is_notebook_execution_environment: bool = Field(
31
+ default=False,
32
+ description="Indication of whether the code is running in a notebook execution environment.",
33
+ examples=[True, False],
34
+ )
30
35
 
31
36
  @property
32
37
  def isb_mesh_service(self) -> MeshService:
@@ -0,0 +1,75 @@
1
+ """Utilities for working with the notebook execution environment."""
2
+
3
+ import time
4
+ from pathlib import Path
5
+
6
+ import nbformat
7
+ from nbconvert import MarkdownExporter
8
+
9
+ DEFAULT_EXPORT_SENTINEL = "MPW_EXPORT_READY"
10
+
11
+
12
+ def wait_for_notebook_save(
13
+ notebook_path: Path | str,
14
+ sentinel: str = DEFAULT_EXPORT_SENTINEL,
15
+ attempts=120,
16
+ delay_s=1,
17
+ ):
18
+ """
19
+ Wait until the notebook on disk contains the sentinel output.
20
+
21
+ Parameters
22
+ ----------
23
+ notebook_path
24
+ The path to the notebook file to check for the sentinel output.
25
+
26
+ sentinel
27
+ The string to search for in the notebook outputs to confirm that the notebook has been saved with the expected output.
28
+
29
+ attempts
30
+ The number of times to check the notebook for the sentinel output before giving up and raising a
31
+ TimeoutError.
32
+
33
+ delay_s
34
+ The number of seconds to wait between attempts to check the notebook for the sentinel output.
35
+
36
+ Returns
37
+ -------
38
+ None if the sentinel is found within the notebook outputs, otherwise raises a TimeoutError after the specified number of attempts.
39
+ """
40
+ print(f"Waiting for notebook to save with sentinel '{sentinel}'...", flush=True)
41
+ for attempt in range(attempts):
42
+ with open(notebook_path, "r", encoding="utf-8") as f:
43
+ nb = nbformat.read(f, as_version=nbformat.NO_CONVERT)
44
+
45
+ # Search outputs
46
+ for cell in nb.cells:
47
+ if cell.cell_type == "code":
48
+ for output in cell.get("outputs", []):
49
+ text = ""
50
+
51
+ if output.output_type == "stream":
52
+ text = output.get("text", "")
53
+ elif output.output_type in ("execute_result", "display_data"):
54
+ text = str(output.get("data", ""))
55
+ if sentinel in text:
56
+ return
57
+ time.sleep(delay_s)
58
+ raise TimeoutError("Notebook did not save updated outputs in time")
59
+
60
+
61
+ def export_notebook_by_path(notebook_path) -> bytes:
62
+ """
63
+ Export the notebook at the given path to markdown format and return the resulting bytes.
64
+
65
+ Parameters
66
+ ----------
67
+ notebook_path
68
+ The path to the notebook file to export.
69
+ """
70
+ with open(notebook_path, "r", encoding="utf-8") as f:
71
+ nb = nbformat.read(f, as_version=4)
72
+
73
+ exporter = MarkdownExporter()
74
+ body, _ = exporter.from_notebook_node(nb)
75
+ return body.encode("utf-8")
@@ -0,0 +1,90 @@
1
+ """Tests for the notebook_utils module"""
2
+
3
+ from pathlib import Path
4
+
5
+ import nbformat
6
+ import pytest
7
+ from nbformat.v4 import new_code_cell
8
+ from nbformat.v4 import new_markdown_cell
9
+ from nbformat.v4 import new_notebook
10
+ from nbformat.v4 import new_output
11
+
12
+ from dkist_processing_core.notebook_utils import DEFAULT_EXPORT_SENTINEL
13
+ from dkist_processing_core.notebook_utils import export_notebook_by_path
14
+ from dkist_processing_core.notebook_utils import wait_for_notebook_save
15
+
16
+
17
+ @pytest.fixture
18
+ def notebook_path_factory(tmp_path: Path):
19
+ def _make_notebook(*, cells=None) -> Path:
20
+ notebook_path = tmp_path / "test.ipynb"
21
+ cells = cells or []
22
+ nb = new_notebook(cells=cells)
23
+ with notebook_path.open("w", encoding="utf-8") as f:
24
+ nbformat.write(nb, f)
25
+ return notebook_path
26
+
27
+ return _make_notebook
28
+
29
+
30
+ @pytest.fixture
31
+ def notebook_with_sentinel_output(notebook_path_factory) -> Path:
32
+ return notebook_path_factory(
33
+ cells=[
34
+ new_markdown_cell("# Title"),
35
+ new_code_cell(
36
+ source="print('ready')",
37
+ outputs=[
38
+ new_output(
39
+ output_type="stream",
40
+ name="stdout",
41
+ text=f"before {DEFAULT_EXPORT_SENTINEL} after",
42
+ )
43
+ ],
44
+ ),
45
+ ]
46
+ )
47
+
48
+
49
+ @pytest.fixture
50
+ def notebook_without_sentinel_output(notebook_path_factory) -> Path:
51
+ return notebook_path_factory(
52
+ cells=[
53
+ new_code_cell(
54
+ source="print('not ready')",
55
+ outputs=[
56
+ new_output(
57
+ output_type="stream",
58
+ name="stdout",
59
+ text="some other output",
60
+ )
61
+ ],
62
+ )
63
+ ]
64
+ )
65
+
66
+
67
+ def test_wait_for_notebook_save_succeeds_when_sentinel_in_code_cell_output(
68
+ notebook_with_sentinel_output,
69
+ ):
70
+ # no error raised
71
+ wait_for_notebook_save(notebook_with_sentinel_output, attempts=1, delay_s=0)
72
+
73
+
74
+ def test_wait_for_notebook_save_times_out_when_sentinel_missing(
75
+ notebook_without_sentinel_output,
76
+ ):
77
+ with pytest.raises(TimeoutError, match="Notebook did not save updated outputs in time"):
78
+ wait_for_notebook_save(notebook_without_sentinel_output, attempts=1, delay_s=0)
79
+
80
+
81
+ def test_export_notebook_by_path_returns_markdown_bytes(notebook_with_sentinel_output) -> None:
82
+ exported = export_notebook_by_path(notebook_with_sentinel_output)
83
+
84
+ assert isinstance(exported, bytes)
85
+
86
+ output = exported.decode("utf-8")
87
+
88
+ assert "# Title" in output # markdown
89
+ assert "print('ready')" in output # code
90
+ assert DEFAULT_EXPORT_SENTINEL in output # output
@@ -9,6 +9,7 @@ from airflow import DAG
9
9
  from dkist_processing_core import ResourceQueue
10
10
  from dkist_processing_core import Workflow
11
11
  from dkist_processing_core.workflow import MAXIMUM_ALLOWED_WORKFLOW_NAME_LENGTH
12
+ from dkist_processing_core.workflow import _check_dag_name_characters
12
13
  from dkist_processing_core.workflow import workflow_name_from_details
13
14
 
14
15
 
@@ -165,9 +166,9 @@ def test_check_dag_name_characters():
165
166
  When: checking if it is a valid airflow name or not
166
167
  Then: correctly identify valid and invalid names
167
168
  """
168
- Workflow.check_dag_name_characters(dag_name="This_dag_name_is_valid")
169
+ _check_dag_name_characters(dag_name="This_dag_name_is_valid")
169
170
  with pytest.raises(ValueError):
170
- Workflow.check_dag_name_characters(dag_name="Invalid*dag*name")
171
+ _check_dag_name_characters(dag_name="Invalid*dag*name")
171
172
 
172
173
 
173
174
  @pytest.mark.parametrize(
@@ -34,6 +34,35 @@ def workflow_name_from_details(
34
34
  return workflow_name
35
35
 
36
36
 
37
+ def _check_dag_name_characters(dag_name: str):
38
+ """
39
+ Figure out if the dag name is an Airflow-allowed name.
40
+
41
+ Can only contain
42
+ * ascii letters
43
+ * numbers
44
+ * dash (-)
45
+ * dot (.)
46
+ * underscore (_)
47
+
48
+ Raise error if non-allowed characters are found.
49
+ """
50
+ allowed_chars = [c for c in string.ascii_letters] + ["-", ".", "_"] + [n for n in string.digits]
51
+ if not all([char in allowed_chars for char in dag_name]):
52
+ raise ValueError(
53
+ f"Dag name {dag_name} contains invalid characters. "
54
+ f"Only ascii letters and the dash, dot, and "
55
+ f"underscore symbols are permitted."
56
+ )
57
+
58
+
59
+ def dag_name(workflow_name: str, workflow_version: str) -> str:
60
+ """Return the dag name created from its constituent parts."""
61
+ result = f"{workflow_name}_{workflow_version}"
62
+ _check_dag_name_characters(result) # raise an error if in valid
63
+ return result
64
+
65
+
37
66
  class Workflow:
38
67
  """
39
68
  Abstraction to create a workflow in 1 or more target execution environment.
@@ -116,33 +145,7 @@ class Workflow:
116
145
  @property
117
146
  def dag_name(self) -> str:
118
147
  """Return the dag name created from its constituent parts."""
119
- result = f"{self.workflow_name}_{self.workflow_version}"
120
- self.check_dag_name_characters(result) # raise an error if in valid
121
- return result
122
-
123
- @staticmethod
124
- def check_dag_name_characters(dag_name: str):
125
- """
126
- Figure out if the dag name is an Airflow-allowed name.
127
-
128
- Can only contain
129
- * ascii letters
130
- * numbers
131
- * dash (-)
132
- * dot (.)
133
- * underscore (_)
134
-
135
- Raise error if non-allowed characters are found.
136
- """
137
- allowed_chars = (
138
- [c for c in string.ascii_letters] + ["-", ".", "_"] + [n for n in string.digits]
139
- )
140
- if not all([char in allowed_chars for char in dag_name]):
141
- raise ValueError(
142
- f"Dag name {dag_name} contains invalid characters. "
143
- f"Only ascii letters and the dash, dot, and "
144
- f"underscore symbols are permitted."
145
- )
148
+ return dag_name(workflow_name=self.workflow_name, workflow_version=self.workflow_version)
146
149
 
147
150
  @property
148
151
  def dag_tags(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-core
3
- Version: 7.2.1
3
+ Version: 7.2.2rc1
4
4
  Summary: Abstraction layer used by the DKIST science data processing pipelines with Apache Airflow
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause
@@ -18,6 +18,8 @@ Requires-Dist: requests>=2.23
18
18
  Requires-Dist: talus<2.0,>=1.3.4
19
19
  Requires-Dist: pendulum
20
20
  Requires-Dist: nbformat>=5.9.2
21
+ Requires-Dist: notebook<8.0,>=7.5.5
22
+ Requires-Dist: nbconvert<8.0,>=7.16.6
21
23
  Requires-Dist: dkist-service-configuration<5.0,>=4.3.0
22
24
  Requires-Dist: pydantic>2.0
23
25
  Provides-Extra: test
@@ -7,11 +7,16 @@ README.rst
7
7
  bitbucket-pipelines.yml
8
8
  pyproject.toml
9
9
  changelog/.gitempty
10
+ changelog/72.misc.rst
11
+ changelog/76.feature.1.rst
12
+ changelog/76.feature.2.rst
13
+ changelog/77.misc.rst
10
14
  dkist_processing_core/__init__.py
11
15
  dkist_processing_core/build_utils.py
12
16
  dkist_processing_core/config.py
13
17
  dkist_processing_core/failure_callback.py
14
18
  dkist_processing_core/node.py
19
+ dkist_processing_core/notebook_utils.py
15
20
  dkist_processing_core/resource_queue.py
16
21
  dkist_processing_core/task.py
17
22
  dkist_processing_core/workflow.py
@@ -27,6 +32,7 @@ dkist_processing_core/tests/test_build_utils.py
27
32
  dkist_processing_core/tests/test_export.py
28
33
  dkist_processing_core/tests/test_failure_callback.py
29
34
  dkist_processing_core/tests/test_node.py
35
+ dkist_processing_core/tests/test_notebook_utils.py
30
36
  dkist_processing_core/tests/test_task.py
31
37
  dkist_processing_core/tests/test_workflow.py
32
38
  dkist_processing_core/tests/invalid_workflow_cyclic/__init__.py
@@ -3,6 +3,8 @@ requests>=2.23
3
3
  talus<2.0,>=1.3.4
4
4
  pendulum
5
5
  nbformat>=5.9.2
6
+ notebook<8.0,>=7.5.5
7
+ nbconvert<8.0,>=7.16.6
6
8
  dkist-service-configuration<5.0,>=4.3.0
7
9
  pydantic>2.0
8
10
 
@@ -26,6 +26,8 @@ dependencies = [
26
26
  "talus >= 1.3.4, <2.0",
27
27
  "pendulum",
28
28
  "nbformat >= 5.9.2",
29
+ "notebook >= 7.5.5, < 8.0",
30
+ "nbconvert >= 7.16.6, < 8.0",
29
31
  "dkist-service-configuration >=4.3.0, <5.0",
30
32
  "pydantic > 2.0",
31
33
  ]