dkist-processing-core 4.0.0__tar.gz → 4.2.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.
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/CHANGELOG.rst +23 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/PKG-INFO +8 -1
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/README.rst +7 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/__init__.py +4 -4
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/build_utils.py +25 -21
- dkist-processing-core-4.0.0/dkist_processing_core/_node.py → dkist-processing-core-4.2.0/dkist_processing_core/node.py +18 -13
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/task.py +7 -6
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/conftest.py +2 -2
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/test_failure_callback.py +5 -5
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/test_node.py +7 -7
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/test_workflow.py +45 -1
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/workflow.py +48 -32
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core.egg-info/PKG-INFO +8 -1
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core.egg-info/SOURCES.txt +2 -2
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core.egg-info/requires.txt +2 -1
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/pyproject.toml +1 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/setup.cfg +2 -1
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/.gitignore +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/.pre-commit-config.yaml +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/.readthedocs.yml +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/bitbucket-pipelines.yml +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/changelog/.gitempty +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/check_changelog_updated.sh +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/config.py +0 -0
- /dkist-processing-core-4.0.0/dkist_processing_core/_failure_callback.py → /dkist-processing-core-4.2.0/dkist_processing_core/failure_callback.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/resource_queue.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/__init__.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/invalid_workflow_cyclic/__init__.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/invalid_workflow_cyclic/workflow.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/invalid_workflow_for_docker_multi_category/__init__.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/invalid_workflow_for_docker_multi_category/workflow.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/task_example.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/test_build_utils.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/test_export.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/test_task.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/valid_workflow_package/__init__.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/valid_workflow_package/workflow.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/zero_node_workflow_package/__init__.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/zero_node_workflow_package/workflow.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core.egg-info/dependency_links.txt +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core.egg-info/top_level.txt +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/Makefile +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/auto-proc-concept-model.png +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/auto_proc_brick.png +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/automated-processing-deployed.png +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/changelog.rst +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/conf.py +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/index.rst +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/make.bat +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/requirements.txt +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/licenses/LICENSE.rst +0 -0
- {dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/setup.py +0 -0
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
v4.2.0 (2024-09-27)
|
|
2
|
+
===================
|
|
3
|
+
|
|
4
|
+
Misc
|
|
5
|
+
----
|
|
6
|
+
|
|
7
|
+
- Fixing deprecation warnings in pkg_resources. (`#39 <https://bitbucket.org/dkistdc/dkist-processing-core/pull-requests/39>`__)
|
|
8
|
+
- Utility for generating the name of a workflow is part of the public API. (`#40 <https://bitbucket.org/dkistdc/dkist-processing-core/pull-requests/40>`__)
|
|
9
|
+
- Upgrade to airflow 2.10.2. (`#41 <https://bitbucket.org/dkistdc/dkist-processing-core/pull-requests/41>`__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
v4.1.0 (2024-07-01)
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Misc
|
|
16
|
+
----
|
|
17
|
+
|
|
18
|
+
- Add utility for generating the name of a workflow. (`#35 <https://bitbucket.org/dkistdc/dkist-processing-core/pull-requests/35>`__)
|
|
19
|
+
- Update the instructions for development to include the dependency on rabbitmq and docker. (`#36 <https://bitbucket.org/dkistdc/dkist-processing-core/pull-requests/36>`__)
|
|
20
|
+
- Make private methods public when we want them to show up in the ReadTheDocs documentation. (`#37 <https://bitbucket.org/dkistdc/dkist-processing-core/pull-requests/37>`__)
|
|
21
|
+
- Upgrade airflow to version 2.9.2. (`#38 <https://bitbucket.org/dkistdc/dkist-processing-core/pull-requests/38>`__)
|
|
22
|
+
|
|
23
|
+
|
|
1
24
|
v4.0.0 (2024-06-03)
|
|
2
25
|
===================
|
|
3
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dkist-processing-core
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: Abstraction layer that is used by the DKIST Science Data Processing pipelines to process DKIST data using Apache Airflow.
|
|
5
5
|
Home-page: https://bitbucket.org/dkistdc/dkist-processing-core/src/main/
|
|
6
6
|
Author: NSO / AURA
|
|
@@ -150,12 +150,19 @@ Environment Variables
|
|
|
150
150
|
|
|
151
151
|
Development
|
|
152
152
|
-----------
|
|
153
|
+
A prerequisite for test execution is a running instance of rabbitmq and docker on the local machine.
|
|
154
|
+
For RabbitMQ the tests will use the default guest/guest credentials and a host ip of 127.0.0.1 and port of 5672 to connect to the broker.
|
|
155
|
+
Getting docker set up varies by system, but the tests will use the default unix socket for the docker daemon.
|
|
156
|
+
|
|
157
|
+
To run the tests locally, clone the repository and install the package in editable mode with the test extras.
|
|
158
|
+
|
|
153
159
|
.. code-block:: bash
|
|
154
160
|
|
|
155
161
|
git clone git@bitbucket.org:dkistdc/dkist-processing-core.git
|
|
156
162
|
cd dkist-processing-core
|
|
157
163
|
pre-commit install
|
|
158
164
|
pip install -e .[test]
|
|
165
|
+
# RabbitMQ and Docker needs to be running
|
|
159
166
|
pytest -v --cov dkist_processing_core
|
|
160
167
|
|
|
161
168
|
Changelog
|
|
@@ -134,12 +134,19 @@ Environment Variables
|
|
|
134
134
|
|
|
135
135
|
Development
|
|
136
136
|
-----------
|
|
137
|
+
A prerequisite for test execution is a running instance of rabbitmq and docker on the local machine.
|
|
138
|
+
For RabbitMQ the tests will use the default guest/guest credentials and a host ip of 127.0.0.1 and port of 5672 to connect to the broker.
|
|
139
|
+
Getting docker set up varies by system, but the tests will use the default unix socket for the docker daemon.
|
|
140
|
+
|
|
141
|
+
To run the tests locally, clone the repository and install the package in editable mode with the test extras.
|
|
142
|
+
|
|
137
143
|
.. code-block:: bash
|
|
138
144
|
|
|
139
145
|
git clone git@bitbucket.org:dkistdc/dkist-processing-core.git
|
|
140
146
|
cd dkist-processing-core
|
|
141
147
|
pre-commit install
|
|
142
148
|
pip install -e .[test]
|
|
149
|
+
# RabbitMQ and Docker needs to be running
|
|
143
150
|
pytest -v --cov dkist_processing_core
|
|
144
151
|
|
|
145
152
|
Changelog
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/__init__.py
RENAMED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Package-level setup information."""
|
|
2
|
-
from
|
|
3
|
-
from
|
|
2
|
+
from importlib.metadata import PackageNotFoundError
|
|
3
|
+
from importlib.metadata import version
|
|
4
4
|
|
|
5
5
|
from dkist_processing_core.resource_queue import ResourceQueue
|
|
6
6
|
from dkist_processing_core.task import TaskBase
|
|
7
7
|
from dkist_processing_core.workflow import Workflow
|
|
8
8
|
|
|
9
9
|
try:
|
|
10
|
-
__version__ =
|
|
11
|
-
except
|
|
10
|
+
__version__ = version(distribution_name=__name__)
|
|
11
|
+
except PackageNotFoundError:
|
|
12
12
|
# package is not installed
|
|
13
13
|
__version__ = "unknown"
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/build_utils.py
RENAMED
|
@@ -16,7 +16,7 @@ def validate_workflows(workflow_package: ModuleType, export_path: Path | None =
|
|
|
16
16
|
rm_export_path_after_test = not bool(export_path)
|
|
17
17
|
if export_path is None:
|
|
18
18
|
export_path = Path("export/")
|
|
19
|
-
workflows =
|
|
19
|
+
workflows = extract_workflows_from_package(workflow_package)
|
|
20
20
|
try:
|
|
21
21
|
_validate_workflows(workflows, export_path)
|
|
22
22
|
finally:
|
|
@@ -24,21 +24,31 @@ def validate_workflows(workflow_package: ModuleType, export_path: Path | None =
|
|
|
24
24
|
rmtree(export_path)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def _validate_workflows(workflows: list[Workflow], export_path: Path) -> None:
|
|
28
|
+
"""Validate workflows by ensuring their exported version compiles as python and that there is at least one node."""
|
|
29
|
+
for w in workflows:
|
|
30
|
+
workflow_py = w.export_dag(path=export_path)
|
|
31
|
+
with workflow_py.open(mode="r") as f:
|
|
32
|
+
compile(f.read(), filename=f"{workflow_py.stem}.pyc", mode="exec")
|
|
33
|
+
if len(w.nodes) == 0:
|
|
34
|
+
raise ValueError(f"Workflow {w.workflow_name} has 0 nodes.")
|
|
35
|
+
|
|
36
|
+
|
|
27
37
|
def export_dags(workflow_package: ModuleType, path: str | Path) -> list[Path]:
|
|
28
38
|
"""Export Airflow DAG files."""
|
|
29
|
-
return [w.export_dag(path=path) for w in
|
|
39
|
+
return [w.export_dag(path=path) for w in extract_workflows_from_package(workflow_package)]
|
|
30
40
|
|
|
31
41
|
|
|
32
42
|
def export_notebooks(workflow_package: ModuleType, path: str | Path) -> list[Path]:
|
|
33
43
|
"""Export Jupyter Notebook files."""
|
|
34
|
-
return [w.export_notebook(path=path) for w in
|
|
44
|
+
return [w.export_notebook(path=path) for w in extract_workflows_from_package(workflow_package)]
|
|
35
45
|
|
|
36
46
|
|
|
37
47
|
def export_notebook_dockerfile(workflow_package: ModuleType, path: str | Path) -> Path:
|
|
38
48
|
"""Export a dockerfile to containerize notebooks."""
|
|
39
49
|
path = Path(path)
|
|
40
50
|
notebook_paths = export_notebooks(workflow_package=workflow_package, path=path)
|
|
41
|
-
category =
|
|
51
|
+
category = extract_category_from_workflows(workflow_package=workflow_package)
|
|
42
52
|
dockerfile = NotebookDockerfile(notebook_paths=notebook_paths, category=category)
|
|
43
53
|
dockerfile_path = Path("Dockerfile")
|
|
44
54
|
dockerfile_path.touch(exist_ok=False)
|
|
@@ -47,8 +57,9 @@ def export_notebook_dockerfile(workflow_package: ModuleType, path: str | Path) -
|
|
|
47
57
|
return dockerfile_path
|
|
48
58
|
|
|
49
59
|
|
|
50
|
-
def
|
|
51
|
-
workflows
|
|
60
|
+
def extract_category_from_workflows(workflow_package: ModuleType) -> str:
|
|
61
|
+
"""Extract the category from the workflows in the package to provide a unique category for the dockerfile."""
|
|
62
|
+
workflows = extract_workflows_from_package(workflow_package)
|
|
52
63
|
categories = {w.category for w in workflows}
|
|
53
64
|
if len(categories) > 1:
|
|
54
65
|
raise ValueError(
|
|
@@ -57,12 +68,14 @@ def _extract_category_from_workflows(workflow_package: ModuleType) -> str:
|
|
|
57
68
|
return categories.pop()
|
|
58
69
|
|
|
59
70
|
|
|
60
|
-
def
|
|
61
|
-
|
|
71
|
+
def extract_workflows_from_package(workflow_package: ModuleType) -> list[Workflow]:
|
|
72
|
+
"""Extract all the Workflow objects from a package."""
|
|
73
|
+
return extract_objects_from_package_by_type(workflow_package, Workflow)
|
|
62
74
|
|
|
63
75
|
|
|
64
|
-
def
|
|
65
|
-
modules
|
|
76
|
+
def extract_objects_from_package_by_type(package: ModuleType, object_type: type) -> list:
|
|
77
|
+
"""Extract all objects in public modules of a given type from a package."""
|
|
78
|
+
modules = parse_unprotected_modules_names_from_package(package)
|
|
66
79
|
objects = []
|
|
67
80
|
for module in modules:
|
|
68
81
|
imported_module = importlib.import_module(f".{module}", package.__name__)
|
|
@@ -70,21 +83,12 @@ def _extract_objects_from_package_by_type(package: ModuleType, object_type: type
|
|
|
70
83
|
return objects
|
|
71
84
|
|
|
72
85
|
|
|
73
|
-
def
|
|
86
|
+
def parse_unprotected_modules_names_from_package(package: ModuleType) -> list[str]:
|
|
87
|
+
"""Parse the names of all modules in a package that are not private i.e. don't begin with an underscore."""
|
|
74
88
|
package_path = Path(package.__path__[0])
|
|
75
89
|
return [m.stem for m in package_path.glob("[!_]*.py")]
|
|
76
90
|
|
|
77
91
|
|
|
78
|
-
def _validate_workflows(workflows: list[Workflow], export_path: Path) -> None:
|
|
79
|
-
"""Validate workflows by ensuring their exported version compiles as python and that there is at least one node."""
|
|
80
|
-
for w in workflows:
|
|
81
|
-
workflow_py = w.export_dag(path=export_path)
|
|
82
|
-
with workflow_py.open(mode="r") as f:
|
|
83
|
-
compile(f.read(), filename=f"{workflow_py.stem}.pyc", mode="exec")
|
|
84
|
-
if len(w.nodes) == 0:
|
|
85
|
-
raise ValueError(f"Workflow {w.workflow_name} has 0 nodes.")
|
|
86
|
-
|
|
87
|
-
|
|
88
92
|
class NotebookDockerfile:
|
|
89
93
|
"""Build a Dockerfile for deployment as a Manual Processing Worker."""
|
|
90
94
|
|
|
@@ -50,7 +50,7 @@ class Node:
|
|
|
50
50
|
def operator(self) -> BashOperator:
|
|
51
51
|
"""Native engine node."""
|
|
52
52
|
from datetime import timedelta
|
|
53
|
-
from dkist_processing_core.
|
|
53
|
+
from dkist_processing_core.failure_callback import chat_ops_notification
|
|
54
54
|
from functools import partial
|
|
55
55
|
|
|
56
56
|
return eval(self.operator_definition)
|
|
@@ -69,7 +69,7 @@ class Node:
|
|
|
69
69
|
"""Airflow style command to define a bash operator."""
|
|
70
70
|
return f"""BashOperator(
|
|
71
71
|
task_id='{self.task.__name__}',
|
|
72
|
-
bash_command='''{self.
|
|
72
|
+
bash_command='''{self.bash_script}''',
|
|
73
73
|
retries={self.task.retries},
|
|
74
74
|
retry_delay=timedelta(seconds={self.task.retry_delay_seconds}),
|
|
75
75
|
on_failure_callback=partial(
|
|
@@ -89,14 +89,15 @@ class Node:
|
|
|
89
89
|
return [(upstream.__name__, self.task.__name__) for upstream in self.upstreams]
|
|
90
90
|
|
|
91
91
|
@property
|
|
92
|
-
def
|
|
92
|
+
def bash_script(self) -> str:
|
|
93
93
|
"""Format bash script for the BashOperator."""
|
|
94
|
-
command = f"""{self.
|
|
95
|
-
{self.
|
|
96
|
-
return self.
|
|
94
|
+
command = f"""{self.install_command}
|
|
95
|
+
{self.run_command}"""
|
|
96
|
+
return self.bash_template(command)
|
|
97
97
|
|
|
98
98
|
@staticmethod
|
|
99
|
-
def
|
|
99
|
+
def bash_template(command: str) -> str:
|
|
100
|
+
"""Return the bash script with a template wrapped command."""
|
|
100
101
|
return f"""#!/bin/bash
|
|
101
102
|
echo Working Directory
|
|
102
103
|
pwd
|
|
@@ -129,26 +130,30 @@ echo Exit with code from main command: $exit_code
|
|
|
129
130
|
exit $exit_code"""
|
|
130
131
|
|
|
131
132
|
@property
|
|
132
|
-
def
|
|
133
|
+
def formatted_pip_extras(self) -> str:
|
|
134
|
+
"""Format pip extras for the installation command."""
|
|
133
135
|
if self.pip_extras:
|
|
134
136
|
extra_requirements = ",".join(self.pip_extras)
|
|
135
137
|
return f"'[{extra_requirements}]'"
|
|
136
138
|
return ""
|
|
137
139
|
|
|
138
140
|
@property
|
|
139
|
-
def
|
|
141
|
+
def install_command(self) -> str:
|
|
142
|
+
"""Format the installation command for the bash script."""
|
|
140
143
|
repo_name = self.workflow_package.split(".")[0].replace("_", "-")
|
|
141
144
|
version = self.workflow_version
|
|
142
|
-
extras = self.
|
|
145
|
+
extras = self.formatted_pip_extras
|
|
143
146
|
return f"""python -m pip install --upgrade pip
|
|
144
147
|
python -m pip install {repo_name}{extras}=={version}"""
|
|
145
148
|
|
|
146
149
|
@property
|
|
147
|
-
def
|
|
148
|
-
|
|
150
|
+
def run_command(self) -> str:
|
|
151
|
+
"""Return the python bash command to execute the task."""
|
|
152
|
+
return f'python -c "{self.python}"'
|
|
149
153
|
|
|
150
154
|
@property
|
|
151
|
-
def
|
|
155
|
+
def python(self) -> str:
|
|
156
|
+
"""Return the python code to execute the task."""
|
|
152
157
|
return f"""from {self.task.__module__} import {self.task.__name__}
|
|
153
158
|
with {self.task.__name__}(recipe_run_id={{{{dag_run.conf['recipe_run_id']}}}}, workflow_name='{self.workflow_name}', workflow_version='{self.workflow_version}') as task:
|
|
154
159
|
task()
|
|
@@ -27,17 +27,18 @@ class ApmTransaction:
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
@property
|
|
30
|
-
def
|
|
30
|
+
def apm_service_name(self) -> str:
|
|
31
|
+
"""Format the service name for Elastic APM."""
|
|
31
32
|
name = f"{self._workflow_name}-{self._workflow_version}"
|
|
32
33
|
name = name.replace("_", "-")
|
|
33
34
|
name = name.replace(".", "-")
|
|
34
35
|
return name
|
|
35
36
|
|
|
36
37
|
@property
|
|
37
|
-
def
|
|
38
|
+
def apm_config(self) -> dict:
|
|
39
|
+
"""Override the Elastic APM configuration with the workflow specific service name."""
|
|
38
40
|
core_config = core_configurations.apm_config
|
|
39
|
-
|
|
40
|
-
core_config["SERVICE_NAME"] = self._apm_service_name
|
|
41
|
+
core_config["SERVICE_NAME"] = self.apm_service_name
|
|
41
42
|
return core_config
|
|
42
43
|
|
|
43
44
|
def __init__(self, transaction_name: str, workflow_name: str, workflow_version: str) -> None:
|
|
@@ -46,10 +47,10 @@ class ApmTransaction:
|
|
|
46
47
|
self.transaction_name = transaction_name
|
|
47
48
|
|
|
48
49
|
if core_configurations.elastic_apm_enabled:
|
|
49
|
-
self.client = elasticapm.Client(self.
|
|
50
|
+
self.client = elasticapm.Client(self.apm_config)
|
|
50
51
|
self.instrument()
|
|
51
52
|
self.client.begin_transaction(transaction_type="Task")
|
|
52
|
-
logger.info(f"APM Configured: {self=} {self.
|
|
53
|
+
logger.info(f"APM Configured: {self=} {self.apm_config=}")
|
|
53
54
|
else:
|
|
54
55
|
logger.warning(f"APM Not Configured")
|
|
55
56
|
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/conftest.py
RENAMED
|
@@ -11,8 +11,8 @@ from talus import DurableProducer
|
|
|
11
11
|
from dkist_processing_core import ResourceQueue
|
|
12
12
|
from dkist_processing_core import TaskBase
|
|
13
13
|
from dkist_processing_core import Workflow
|
|
14
|
-
from dkist_processing_core.
|
|
15
|
-
from dkist_processing_core.
|
|
14
|
+
from dkist_processing_core.node import Node
|
|
15
|
+
from dkist_processing_core.node import task_type_hint
|
|
16
16
|
from dkist_processing_core.tests.task_example import Task
|
|
17
17
|
|
|
18
18
|
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import pytest
|
|
3
3
|
from talus import DurableProducer
|
|
4
4
|
|
|
5
|
-
from dkist_processing_core.
|
|
6
|
-
from dkist_processing_core.
|
|
7
|
-
from dkist_processing_core.
|
|
8
|
-
from dkist_processing_core.
|
|
9
|
-
from dkist_processing_core.
|
|
5
|
+
from dkist_processing_core.failure_callback import chat_ops_notification
|
|
6
|
+
from dkist_processing_core.failure_callback import parse_dag_run_id_from_context
|
|
7
|
+
from dkist_processing_core.failure_callback import parse_log_url_from_context
|
|
8
|
+
from dkist_processing_core.failure_callback import recipe_run_failure_message_producer_factory
|
|
9
|
+
from dkist_processing_core.failure_callback import RecipeRunFailureMessage
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@pytest.fixture()
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/test_node.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Tests of the
|
|
1
|
+
"""Tests of the node.py module."""
|
|
2
2
|
import subprocess
|
|
3
3
|
from subprocess import CalledProcessError
|
|
4
4
|
from typing import Callable
|
|
@@ -8,7 +8,7 @@ from airflow.operators.bash import BashOperator
|
|
|
8
8
|
from jinja2 import Template
|
|
9
9
|
|
|
10
10
|
from dkist_processing_core import ResourceQueue
|
|
11
|
-
from dkist_processing_core.
|
|
11
|
+
from dkist_processing_core.node import Node
|
|
12
12
|
from dkist_processing_core.tests.task_example import Task
|
|
13
13
|
|
|
14
14
|
|
|
@@ -38,8 +38,8 @@ def test_nodes(node, fake_producer_factory, queue_name, pip_extras):
|
|
|
38
38
|
# passing in just a context dict positional arg with a fake http adapter does not raise an error
|
|
39
39
|
failure_callback_func({"context": True}, producer_factory=fake_producer_factory)
|
|
40
40
|
assert isinstance(operator, BashOperator)
|
|
41
|
-
assert node.
|
|
42
|
-
assert node.
|
|
41
|
+
assert node.install_command in operator.bash_command
|
|
42
|
+
assert node.python in operator.bash_command
|
|
43
43
|
assert node.workflow_name == name
|
|
44
44
|
assert node.upstreams == upstream
|
|
45
45
|
assert node.task == task
|
|
@@ -57,7 +57,7 @@ def test_node_bash_template_return_0(node):
|
|
|
57
57
|
"""
|
|
58
58
|
node, *args = node
|
|
59
59
|
cmd = 'python -c "pass"'
|
|
60
|
-
result = subprocess.run(node.
|
|
60
|
+
result = subprocess.run(node.bash_template(cmd), shell=True, check=True)
|
|
61
61
|
assert result.returncode == 0
|
|
62
62
|
|
|
63
63
|
|
|
@@ -71,7 +71,7 @@ def test_node_bash_template_return_1(node):
|
|
|
71
71
|
node, *args = node
|
|
72
72
|
cmd = 'python -c "raise Exception"'
|
|
73
73
|
with pytest.raises(CalledProcessError):
|
|
74
|
-
subprocess.run(node.
|
|
74
|
+
subprocess.run(node.bash_template(cmd), shell=True, check=True)
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def test_node_python(single_node):
|
|
@@ -81,7 +81,7 @@ def test_node_python(single_node):
|
|
|
81
81
|
Then: no exceptions raised.
|
|
82
82
|
"""
|
|
83
83
|
# Given
|
|
84
|
-
code_template = Template(single_node.
|
|
84
|
+
code_template = Template(single_node.python)
|
|
85
85
|
|
|
86
86
|
class RenderData:
|
|
87
87
|
def __init__(self):
|
|
@@ -8,6 +8,7 @@ from airflow import DAG
|
|
|
8
8
|
from dkist_processing_core import ResourceQueue
|
|
9
9
|
from dkist_processing_core import Workflow
|
|
10
10
|
from dkist_processing_core.workflow import MAXIMUM_ALLOWED_WORKFLOW_NAME_LENGTH
|
|
11
|
+
from dkist_processing_core.workflow import workflow_name_from_details
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def test_workflow_metadata(workflow):
|
|
@@ -39,7 +40,7 @@ def test_workflow_metadata(workflow):
|
|
|
39
40
|
assert workflow_instance.input_data == input_data
|
|
40
41
|
assert workflow_instance.output_data == output_data
|
|
41
42
|
assert workflow_instance.detail == detail
|
|
42
|
-
assert sorted(json.loads(workflow_instance.
|
|
43
|
+
assert sorted(json.loads(workflow_instance.dag_tags)) == sorted(
|
|
43
44
|
[tag for tag in tags] + [input_data, output_data, category, version]
|
|
44
45
|
)
|
|
45
46
|
|
|
@@ -166,3 +167,46 @@ def test_check_dag_name_characters():
|
|
|
166
167
|
Workflow.check_dag_name_characters(dag_name="This_dag_name_is_valid")
|
|
167
168
|
with pytest.raises(ValueError):
|
|
168
169
|
Workflow.check_dag_name_characters(dag_name="Invalid*dag*name")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.mark.parametrize(
|
|
173
|
+
"detail",
|
|
174
|
+
[
|
|
175
|
+
pytest.param(None, id="no_detail"),
|
|
176
|
+
pytest.param("detail", id="with_detail"),
|
|
177
|
+
],
|
|
178
|
+
)
|
|
179
|
+
def test_workflow_name_from_details(detail: str | None):
|
|
180
|
+
"""
|
|
181
|
+
Given: a set of details
|
|
182
|
+
When: creating a workflow name
|
|
183
|
+
Then: the workflow name is created correctly
|
|
184
|
+
"""
|
|
185
|
+
input_data = "input"
|
|
186
|
+
output_data = "output"
|
|
187
|
+
category = "instrument"
|
|
188
|
+
expected_workflow_name = f"{input_data}_to_{output_data}_{category}"
|
|
189
|
+
if detail:
|
|
190
|
+
expected_workflow_name += f"_{detail}"
|
|
191
|
+
workflow_name = workflow_name_from_details(
|
|
192
|
+
input_data=input_data,
|
|
193
|
+
output_data=output_data,
|
|
194
|
+
category=category,
|
|
195
|
+
detail=detail,
|
|
196
|
+
)
|
|
197
|
+
assert workflow_name == expected_workflow_name
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_workflow_name_from_details_too_long():
|
|
201
|
+
"""
|
|
202
|
+
Given: workflow details with a long input_data value
|
|
203
|
+
When: calling workflow_name_from_details
|
|
204
|
+
Then: a ValueError is raised
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
with pytest.raises(ValueError):
|
|
208
|
+
workflow_name = workflow_name_from_details(
|
|
209
|
+
input_data="".join(["a" for _ in range(MAXIMUM_ALLOWED_WORKFLOW_NAME_LENGTH)]),
|
|
210
|
+
output_data="",
|
|
211
|
+
category="",
|
|
212
|
+
)
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/workflow.py
RENAMED
|
@@ -8,16 +8,32 @@ import nbformat as nbf
|
|
|
8
8
|
from airflow import DAG
|
|
9
9
|
|
|
10
10
|
from dkist_processing_core import ResourceQueue
|
|
11
|
-
from dkist_processing_core._node import Node
|
|
12
|
-
from dkist_processing_core._node import task_type_hint
|
|
13
|
-
from dkist_processing_core._node import upstreams_type_hint
|
|
14
11
|
from dkist_processing_core.config import core_configurations
|
|
12
|
+
from dkist_processing_core.node import Node
|
|
13
|
+
from dkist_processing_core.node import task_type_hint
|
|
14
|
+
from dkist_processing_core.node import upstreams_type_hint
|
|
15
15
|
|
|
16
|
-
__all__ = ["Workflow"]
|
|
16
|
+
__all__ = ["Workflow", "workflow_name_from_details"]
|
|
17
17
|
|
|
18
18
|
MAXIMUM_ALLOWED_WORKFLOW_NAME_LENGTH = 100
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
def workflow_name_from_details(
|
|
22
|
+
input_data: str, output_data: str, category: str, detail: str | None = None
|
|
23
|
+
) -> str:
|
|
24
|
+
"""Create the workflow name from its constituent parts."""
|
|
25
|
+
workflow_name = f"{input_data}_to_{output_data}_{category}"
|
|
26
|
+
if detail:
|
|
27
|
+
workflow_name += f"_{detail}"
|
|
28
|
+
workflow_name_too_long = len(workflow_name) > MAXIMUM_ALLOWED_WORKFLOW_NAME_LENGTH
|
|
29
|
+
if workflow_name_too_long:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
f"Workflow name {workflow_name} is {len(workflow_name)} characters long. "
|
|
32
|
+
f"Limit is {MAXIMUM_ALLOWED_WORKFLOW_NAME_LENGTH} characters."
|
|
33
|
+
)
|
|
34
|
+
return workflow_name
|
|
35
|
+
|
|
36
|
+
|
|
21
37
|
class Workflow:
|
|
22
38
|
"""
|
|
23
39
|
Abstraction to create a workflow in 1 or more target execution environment.
|
|
@@ -84,25 +100,21 @@ class Workflow:
|
|
|
84
100
|
if isinstance(tags, str):
|
|
85
101
|
tags = [tags]
|
|
86
102
|
self.tags: list[str] = tags or []
|
|
87
|
-
self._dag = self.
|
|
103
|
+
self._dag = self.initialize_local_dag()
|
|
88
104
|
self.nodes = []
|
|
89
105
|
|
|
90
106
|
@property
|
|
91
107
|
def workflow_name(self) -> str:
|
|
92
108
|
"""Return the workflow name created from its constituent parts."""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
f"Workflow name {result} is {len(result)} characters long. "
|
|
100
|
-
f"Limit is {MAXIMUM_ALLOWED_WORKFLOW_NAME_LENGTH} characters."
|
|
101
|
-
)
|
|
102
|
-
return result
|
|
109
|
+
return workflow_name_from_details(
|
|
110
|
+
input_data=self.input_data,
|
|
111
|
+
output_data=self.output_data,
|
|
112
|
+
category=self.category,
|
|
113
|
+
detail=self.detail,
|
|
114
|
+
)
|
|
103
115
|
|
|
104
116
|
@property
|
|
105
|
-
def
|
|
117
|
+
def dag_name(self) -> str:
|
|
106
118
|
"""Return the dag name created from its constituent parts."""
|
|
107
119
|
result = f"{self.workflow_name}_{self.workflow_version}"
|
|
108
120
|
self.check_dag_name_characters(result) # raise an error if in valid
|
|
@@ -133,7 +145,7 @@ class Workflow:
|
|
|
133
145
|
)
|
|
134
146
|
|
|
135
147
|
@property
|
|
136
|
-
def
|
|
148
|
+
def dag_tags(self) -> str:
|
|
137
149
|
"""
|
|
138
150
|
Return the list of dag tags to be used in Airflow's UI.
|
|
139
151
|
|
|
@@ -146,14 +158,15 @@ class Workflow:
|
|
|
146
158
|
return json.dumps(tags)
|
|
147
159
|
|
|
148
160
|
@property
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
return f"DAG(dag_id='{self.
|
|
161
|
+
def dag_definition(self) -> str:
|
|
162
|
+
"""Return the string representation of the DAG object instantiation."""
|
|
163
|
+
return f"DAG(dag_id='{self.dag_name}', start_date=pendulum.today('UTC').add(days=-2), schedule=None, catchup=False, tags={self.dag_tags})"
|
|
152
164
|
|
|
153
|
-
def
|
|
165
|
+
def initialize_local_dag(self) -> DAG:
|
|
166
|
+
"""Create a local instance of the DAG object."""
|
|
154
167
|
import pendulum
|
|
155
168
|
|
|
156
|
-
return eval(self.
|
|
169
|
+
return eval(self.dag_definition)
|
|
157
170
|
|
|
158
171
|
def add_node(
|
|
159
172
|
self,
|
|
@@ -190,22 +203,22 @@ class Workflow:
|
|
|
190
203
|
path = path or "dags/"
|
|
191
204
|
path = Path(path)
|
|
192
205
|
path.mkdir(exist_ok=True)
|
|
193
|
-
workflow_py = path / f"{self.
|
|
206
|
+
workflow_py = path / f"{self.dag_name}.py"
|
|
194
207
|
|
|
195
208
|
with workflow_py.open(mode="w") as f:
|
|
196
209
|
f.write(
|
|
197
210
|
f"# {self.workflow_name} workflow version {self.workflow_version} definition rendered for airflow scheduler\n"
|
|
198
211
|
)
|
|
199
|
-
f.write(self.
|
|
212
|
+
f.write(self.workflow_imports)
|
|
200
213
|
f.write("# Workflow\n")
|
|
201
|
-
f.write(self.
|
|
214
|
+
f.write(self.workflow_instantiation)
|
|
202
215
|
f.write(" # Nodes\n")
|
|
203
216
|
for n in self.nodes:
|
|
204
217
|
operator = f"{n.task.__name__.lower()}_operator"
|
|
205
218
|
f.write(f" {operator} = {n.operator_definition}")
|
|
206
219
|
f.write("\n")
|
|
207
220
|
f.write(" # Edges\n")
|
|
208
|
-
f.write(self.
|
|
221
|
+
f.write(self.workflow_edges)
|
|
209
222
|
f.write("\n")
|
|
210
223
|
return workflow_py
|
|
211
224
|
|
|
@@ -214,7 +227,7 @@ class Workflow:
|
|
|
214
227
|
path = path or "notebooks/"
|
|
215
228
|
path = Path(path)
|
|
216
229
|
path.mkdir(exist_ok=True)
|
|
217
|
-
notebook_ipynb = path / f"{self.
|
|
230
|
+
notebook_ipynb = path / f"{self.dag_name}.ipynb"
|
|
218
231
|
|
|
219
232
|
nb = nbf.v4.new_notebook()
|
|
220
233
|
nb["cells"].append(nbf.v4.new_code_cell("recipe_run_id: int ="))
|
|
@@ -233,7 +246,8 @@ class Workflow:
|
|
|
233
246
|
return valid_node_order
|
|
234
247
|
|
|
235
248
|
@property
|
|
236
|
-
def
|
|
249
|
+
def workflow_imports(self) -> str:
|
|
250
|
+
"""Return the import statements for the workflow."""
|
|
237
251
|
imports = [
|
|
238
252
|
"from datetime import timedelta",
|
|
239
253
|
"from functools import partial",
|
|
@@ -242,18 +256,20 @@ class Workflow:
|
|
|
242
256
|
"from airflow.operators.bash import BashOperator",
|
|
243
257
|
"import pendulum",
|
|
244
258
|
"",
|
|
245
|
-
"from dkist_processing_core.
|
|
259
|
+
"from dkist_processing_core.failure_callback import chat_ops_notification",
|
|
246
260
|
"",
|
|
247
261
|
"",
|
|
248
262
|
]
|
|
249
263
|
return "\n".join(imports)
|
|
250
264
|
|
|
251
265
|
@property
|
|
252
|
-
def
|
|
253
|
-
|
|
266
|
+
def workflow_instantiation(self) -> str:
|
|
267
|
+
"""Return the context manager instantiation of the workflow object."""
|
|
268
|
+
return f"with {self.dag_definition} as d:\n pass\n"
|
|
254
269
|
|
|
255
270
|
@property
|
|
256
|
-
def
|
|
271
|
+
def workflow_edges(self) -> str:
|
|
272
|
+
"""Return the edges between nodes for the workflow."""
|
|
257
273
|
edges = []
|
|
258
274
|
for n in self.nodes:
|
|
259
275
|
for upstream, downstream in n.dependencies:
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dkist-processing-core
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: Abstraction layer that is used by the DKIST Science Data Processing pipelines to process DKIST data using Apache Airflow.
|
|
5
5
|
Home-page: https://bitbucket.org/dkistdc/dkist-processing-core/src/main/
|
|
6
6
|
Author: NSO / AURA
|
|
@@ -150,12 +150,19 @@ Environment Variables
|
|
|
150
150
|
|
|
151
151
|
Development
|
|
152
152
|
-----------
|
|
153
|
+
A prerequisite for test execution is a running instance of rabbitmq and docker on the local machine.
|
|
154
|
+
For RabbitMQ the tests will use the default guest/guest credentials and a host ip of 127.0.0.1 and port of 5672 to connect to the broker.
|
|
155
|
+
Getting docker set up varies by system, but the tests will use the default unix socket for the docker daemon.
|
|
156
|
+
|
|
157
|
+
To run the tests locally, clone the repository and install the package in editable mode with the test extras.
|
|
158
|
+
|
|
153
159
|
.. code-block:: bash
|
|
154
160
|
|
|
155
161
|
git clone git@bitbucket.org:dkistdc/dkist-processing-core.git
|
|
156
162
|
cd dkist-processing-core
|
|
157
163
|
pre-commit install
|
|
158
164
|
pip install -e .[test]
|
|
165
|
+
# RabbitMQ and Docker needs to be running
|
|
159
166
|
pytest -v --cov dkist_processing_core
|
|
160
167
|
|
|
161
168
|
Changelog
|
|
@@ -10,10 +10,10 @@ setup.cfg
|
|
|
10
10
|
setup.py
|
|
11
11
|
changelog/.gitempty
|
|
12
12
|
dkist_processing_core/__init__.py
|
|
13
|
-
dkist_processing_core/_failure_callback.py
|
|
14
|
-
dkist_processing_core/_node.py
|
|
15
13
|
dkist_processing_core/build_utils.py
|
|
16
14
|
dkist_processing_core/config.py
|
|
15
|
+
dkist_processing_core/failure_callback.py
|
|
16
|
+
dkist_processing_core/node.py
|
|
17
17
|
dkist_processing_core/resource_queue.py
|
|
18
18
|
dkist_processing_core/task.py
|
|
19
19
|
dkist_processing_core/workflow.py
|
|
@@ -19,7 +19,7 @@ setup_requires = setuptools_scm
|
|
|
19
19
|
packages = find:
|
|
20
20
|
include_package_data = True
|
|
21
21
|
install_requires =
|
|
22
|
-
apache-airflow[postgres, celery] == 2.
|
|
22
|
+
apache-airflow[postgres, celery] == 2.10.2
|
|
23
23
|
elastic-apm < 7.0.0
|
|
24
24
|
requests >= 2.23
|
|
25
25
|
talus >= 1.1.0, <2.0
|
|
@@ -36,6 +36,7 @@ test =
|
|
|
36
36
|
jinja2
|
|
37
37
|
towncrier
|
|
38
38
|
nbconvert
|
|
39
|
+
ipython
|
|
39
40
|
docs =
|
|
40
41
|
sphinx
|
|
41
42
|
sphinx-astropy
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/resource_queue.py
RENAMED
|
File without changes
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/__init__.py
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
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/dkist_processing_core/tests/test_task.py
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
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/auto-proc-concept-model.png
RENAMED
|
File without changes
|
|
File without changes
|
{dkist-processing-core-4.0.0 → dkist-processing-core-4.2.0}/docs/automated-processing-deployed.png
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
|