jobflow 0.1.18__tar.gz → 0.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.
- {jobflow-0.1.18/src/jobflow.egg-info → jobflow-0.2.0}/PKG-INFO +28 -26
- {jobflow-0.1.18 → jobflow-0.2.0}/pyproject.toml +27 -25
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/core/flow.py +47 -4
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/core/job.py +30 -14
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/core/maker.py +1 -1
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/core/reference.py +1 -1
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/managers/local.py +8 -5
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/settings.py +13 -2
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/utils/graph.py +1 -1
- jobflow-0.2.0/src/jobflow/utils/log.py +41 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/utils/uuid.py +4 -1
- {jobflow-0.1.18 → jobflow-0.2.0/src/jobflow.egg-info}/PKG-INFO +28 -26
- jobflow-0.2.0/src/jobflow.egg-info/requires.txt +52 -0
- jobflow-0.1.18/src/jobflow/utils/log.py +0 -29
- jobflow-0.1.18/src/jobflow.egg-info/requires.txt +0 -52
- {jobflow-0.1.18 → jobflow-0.2.0}/LICENSE +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/README.md +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/setup.cfg +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/__init__.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/_version.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/core/__init__.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/core/schemas.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/core/state.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/core/store.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/managers/__init__.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/managers/fireworks.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/py.typed +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/utils/__init__.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/utils/dict_mods.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/utils/enum.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/utils/find.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow/utils/uid.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow.egg-info/SOURCES.txt +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow.egg-info/dependency_links.txt +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/src/jobflow.egg-info/top_level.txt +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/tests/test_settings.py +0 -0
- {jobflow-0.1.18 → jobflow-0.2.0}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: jobflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: jobflow is a library for writing computational workflows
|
|
5
5
|
Author-email: Alex Ganose <a.ganose@imperial.ac.uk>
|
|
6
6
|
License: modified BSD
|
|
@@ -17,11 +17,12 @@ Classifier: Operating System :: OS Independent
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
22
|
Classifier: Topic :: Database :: Front-Ends
|
|
22
23
|
Classifier: Topic :: Other/Nonlisted Topic
|
|
23
24
|
Classifier: Topic :: Scientific/Engineering
|
|
24
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.10
|
|
25
26
|
Description-Content-Type: text/markdown
|
|
26
27
|
License-File: LICENSE
|
|
27
28
|
Requires-Dist: PyYAML
|
|
@@ -34,39 +35,40 @@ Requires-Dist: pydash
|
|
|
34
35
|
Provides-Extra: ulid
|
|
35
36
|
Requires-Dist: python-ulid; extra == "ulid"
|
|
36
37
|
Provides-Extra: docs
|
|
37
|
-
Requires-Dist: autodoc_pydantic==2.
|
|
38
|
-
Requires-Dist: furo==2024.
|
|
39
|
-
Requires-Dist: ipython==
|
|
40
|
-
Requires-Dist: myst_parser==
|
|
41
|
-
Requires-Dist: nbsphinx==0.9.
|
|
38
|
+
Requires-Dist: autodoc_pydantic==2.2.0; extra == "docs"
|
|
39
|
+
Requires-Dist: furo==2024.8.6; extra == "docs"
|
|
40
|
+
Requires-Dist: ipython==9.3.0; extra == "docs"
|
|
41
|
+
Requires-Dist: myst_parser==4.0.1; extra == "docs"
|
|
42
|
+
Requires-Dist: nbsphinx==0.9.7; extra == "docs"
|
|
42
43
|
Requires-Dist: sphinx-copybutton==0.5.2; extra == "docs"
|
|
43
|
-
Requires-Dist: sphinx==
|
|
44
|
+
Requires-Dist: sphinx==8.1.3; extra == "docs"
|
|
44
45
|
Provides-Extra: dev
|
|
45
46
|
Requires-Dist: pre-commit>=2.12.1; extra == "dev"
|
|
46
47
|
Requires-Dist: typing_extensions; python_version < "3.11" and extra == "dev"
|
|
47
48
|
Provides-Extra: tests
|
|
48
|
-
Requires-Dist: moto==
|
|
49
|
-
Requires-Dist: pytest-cov==
|
|
50
|
-
Requires-Dist: pytest==8.
|
|
49
|
+
Requires-Dist: moto==5.1.5; extra == "tests"
|
|
50
|
+
Requires-Dist: pytest-cov==6.1.1; extra == "tests"
|
|
51
|
+
Requires-Dist: pytest==8.4.0; extra == "tests"
|
|
51
52
|
Provides-Extra: vis
|
|
52
53
|
Requires-Dist: matplotlib; extra == "vis"
|
|
53
54
|
Requires-Dist: pydot; extra == "vis"
|
|
54
55
|
Provides-Extra: fireworks
|
|
55
56
|
Requires-Dist: FireWorks; extra == "fireworks"
|
|
56
57
|
Provides-Extra: strict
|
|
57
|
-
Requires-Dist: FireWorks==2.0.
|
|
58
|
-
Requires-Dist: PyYAML==6.0.
|
|
59
|
-
Requires-Dist: maggma==0.
|
|
60
|
-
Requires-Dist: matplotlib==3.
|
|
61
|
-
Requires-Dist: monty==
|
|
62
|
-
Requires-Dist: moto==
|
|
63
|
-
Requires-Dist: networkx==3.2
|
|
64
|
-
Requires-Dist: pydantic-settings==2.
|
|
65
|
-
Requires-Dist: pydantic==2.
|
|
66
|
-
Requires-Dist: pydash==8.0.
|
|
67
|
-
Requires-Dist: pydot==
|
|
68
|
-
Requires-Dist: python-ulid==
|
|
69
|
-
Requires-Dist: typing-extensions==4.
|
|
58
|
+
Requires-Dist: FireWorks==2.0.4; extra == "strict"
|
|
59
|
+
Requires-Dist: PyYAML==6.0.2; extra == "strict"
|
|
60
|
+
Requires-Dist: maggma==0.71.5; extra == "strict"
|
|
61
|
+
Requires-Dist: matplotlib==3.10.3; extra == "strict"
|
|
62
|
+
Requires-Dist: monty==2025.3.3; extra == "strict"
|
|
63
|
+
Requires-Dist: moto==5.1.5; extra == "strict"
|
|
64
|
+
Requires-Dist: networkx==3.4.2; extra == "strict"
|
|
65
|
+
Requires-Dist: pydantic-settings==2.9.1; extra == "strict"
|
|
66
|
+
Requires-Dist: pydantic==2.11.5; extra == "strict"
|
|
67
|
+
Requires-Dist: pydash==8.0.5; extra == "strict"
|
|
68
|
+
Requires-Dist: pydot==4.0.0; extra == "strict"
|
|
69
|
+
Requires-Dist: python-ulid==3.0.0; extra == "strict"
|
|
70
|
+
Requires-Dist: typing-extensions==4.13.2; extra == "strict"
|
|
71
|
+
Dynamic: license-file
|
|
70
72
|
|
|
71
73
|
<div align="center">
|
|
72
74
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools >= 42", "versioningit
|
|
2
|
+
requires = ["setuptools >= 42", "versioningit >= 1,< 4", "wheel"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -19,12 +19,13 @@ classifiers = [
|
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
"Programming Language :: Python :: 3.10",
|
|
21
21
|
"Programming Language :: Python :: 3.11",
|
|
22
|
-
"Programming Language :: Python :: 3.
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
23
24
|
"Topic :: Database :: Front-Ends",
|
|
24
25
|
"Topic :: Other/Nonlisted Topic",
|
|
25
26
|
"Topic :: Scientific/Engineering",
|
|
26
27
|
]
|
|
27
|
-
requires-python = ">=3.
|
|
28
|
+
requires-python = ">=3.10"
|
|
28
29
|
dependencies = [
|
|
29
30
|
"PyYAML",
|
|
30
31
|
"maggma>=0.57.0",
|
|
@@ -38,32 +39,32 @@ dependencies = [
|
|
|
38
39
|
[project.optional-dependencies]
|
|
39
40
|
ulid = ["python-ulid"]
|
|
40
41
|
docs = [
|
|
41
|
-
"autodoc_pydantic==2.
|
|
42
|
-
"furo==2024.
|
|
43
|
-
"ipython==
|
|
44
|
-
"myst_parser==
|
|
45
|
-
"nbsphinx==0.9.
|
|
42
|
+
"autodoc_pydantic==2.2.0",
|
|
43
|
+
"furo==2024.8.6",
|
|
44
|
+
"ipython==9.3.0",
|
|
45
|
+
"myst_parser==4.0.1",
|
|
46
|
+
"nbsphinx==0.9.7",
|
|
46
47
|
"sphinx-copybutton==0.5.2",
|
|
47
|
-
"sphinx==
|
|
48
|
+
"sphinx==8.1.3",
|
|
48
49
|
]
|
|
49
50
|
dev = ["pre-commit>=2.12.1", "typing_extensions; python_version < '3.11'"]
|
|
50
|
-
tests = ["moto==
|
|
51
|
+
tests = ["moto==5.1.5", "pytest-cov==6.1.1", "pytest==8.4.0"]
|
|
51
52
|
vis = ["matplotlib", "pydot"]
|
|
52
53
|
fireworks = ["FireWorks"]
|
|
53
54
|
strict = [
|
|
54
|
-
"FireWorks==2.0.
|
|
55
|
-
"PyYAML==6.0.
|
|
56
|
-
"maggma==0.
|
|
57
|
-
"matplotlib==3.
|
|
58
|
-
"monty==
|
|
59
|
-
"moto==
|
|
60
|
-
"networkx==3.2
|
|
61
|
-
"pydantic-settings==2.
|
|
62
|
-
"pydantic==2.
|
|
63
|
-
"pydash==8.0.
|
|
64
|
-
"pydot==
|
|
65
|
-
"python-ulid==
|
|
66
|
-
"typing-extensions==4.
|
|
55
|
+
"FireWorks==2.0.4",
|
|
56
|
+
"PyYAML==6.0.2",
|
|
57
|
+
"maggma==0.71.5",
|
|
58
|
+
"matplotlib==3.10.3",
|
|
59
|
+
"monty==2025.3.3",
|
|
60
|
+
"moto==5.1.5",
|
|
61
|
+
"networkx==3.4.2",
|
|
62
|
+
"pydantic-settings==2.9.1",
|
|
63
|
+
"pydantic==2.11.5",
|
|
64
|
+
"pydash==8.0.5",
|
|
65
|
+
"pydot==4.0.0",
|
|
66
|
+
"python-ulid==3.0.0",
|
|
67
|
+
"typing-extensions==4.13.2",
|
|
67
68
|
]
|
|
68
69
|
|
|
69
70
|
[project.urls]
|
|
@@ -84,7 +85,7 @@ max-line-length = 88
|
|
|
84
85
|
max-doc-length = 88
|
|
85
86
|
select = "C, E, F, W, B"
|
|
86
87
|
extend-ignore = "E203, W503, E501, F401, RST21"
|
|
87
|
-
min-python-version = "3.
|
|
88
|
+
min-python-version = "3.10.0"
|
|
88
89
|
docstring-convention = "numpy"
|
|
89
90
|
rst-roles = "class, func, ref, obj"
|
|
90
91
|
|
|
@@ -121,6 +122,7 @@ exclude_lines = [
|
|
|
121
122
|
|
|
122
123
|
[tool.ruff]
|
|
123
124
|
target-version = "py39"
|
|
125
|
+
output-format = "concise"
|
|
124
126
|
|
|
125
127
|
[tool.ruff.lint]
|
|
126
128
|
select = [
|
|
@@ -171,7 +173,6 @@ ignore = [
|
|
|
171
173
|
]
|
|
172
174
|
pydocstyle.convention = "numpy"
|
|
173
175
|
isort.known-first-party = ["jobflow"]
|
|
174
|
-
ignore-init-module-imports = true
|
|
175
176
|
|
|
176
177
|
[tool.ruff.lint.per-file-ignores]
|
|
177
178
|
# F401: unused import
|
|
@@ -180,3 +181,4 @@ ignore-init-module-imports = true
|
|
|
180
181
|
# PLR2004: magic-value-comparison
|
|
181
182
|
# PT004: pytest-missing-fixture-name-underscore
|
|
182
183
|
"**/tests/*" = ["ANN", "ARG001", "D", "PLR2004", "PT004", "S101"]
|
|
184
|
+
"docs/tutorials/*" = ["D", "PLR2004"]
|
|
@@ -67,6 +67,11 @@ class Flow(MSONable):
|
|
|
67
67
|
automatically when a flow is included in the jobs array of another flow.
|
|
68
68
|
The object identified by one UUID of the list should be contained in objects
|
|
69
69
|
identified by its subsequent elements.
|
|
70
|
+
metadata
|
|
71
|
+
A dictionary of information that will get stored in the Flow collection.
|
|
72
|
+
metadata_updates
|
|
73
|
+
A list of updates for the metadata that will be applied to any dynamically
|
|
74
|
+
generated sub Flow/Job.
|
|
70
75
|
|
|
71
76
|
Raises
|
|
72
77
|
------
|
|
@@ -128,6 +133,8 @@ class Flow(MSONable):
|
|
|
128
133
|
order: JobOrder = JobOrder.AUTO,
|
|
129
134
|
uuid: str = None,
|
|
130
135
|
hosts: list[str] = None,
|
|
136
|
+
metadata: dict[str, Any] = None,
|
|
137
|
+
metadata_updates: list[dict[str, Any]] = None,
|
|
131
138
|
):
|
|
132
139
|
from jobflow.core.job import Job
|
|
133
140
|
|
|
@@ -141,6 +148,8 @@ class Flow(MSONable):
|
|
|
141
148
|
self.order = order
|
|
142
149
|
self.uuid = uuid
|
|
143
150
|
self.hosts = hosts or []
|
|
151
|
+
self.metadata = metadata or {}
|
|
152
|
+
self.metadata_updates = metadata_updates or []
|
|
144
153
|
|
|
145
154
|
self._jobs: tuple[Flow | Job, ...] = ()
|
|
146
155
|
self.add_jobs(jobs)
|
|
@@ -190,7 +199,7 @@ class Flow(MSONable):
|
|
|
190
199
|
if other not in self:
|
|
191
200
|
raise ValueError(f"{other!r} not found in flow")
|
|
192
201
|
new_flow = deepcopy(self)
|
|
193
|
-
new_flow.jobs = tuple(
|
|
202
|
+
new_flow.jobs = tuple(job for job in new_flow if job != other)
|
|
194
203
|
return new_flow
|
|
195
204
|
|
|
196
205
|
def __repr__(self, level: int = 0, prefix: str = "") -> str:
|
|
@@ -608,9 +617,10 @@ class Flow(MSONable):
|
|
|
608
617
|
function_filter: Callable = None,
|
|
609
618
|
dict_mod: bool = False,
|
|
610
619
|
dynamic: bool = True,
|
|
620
|
+
callback_filter: Callable[[Flow | Job], bool] | None = None,
|
|
611
621
|
):
|
|
612
622
|
"""
|
|
613
|
-
Update the metadata of
|
|
623
|
+
Update the metadata of the Flow and/or its Jobs.
|
|
614
624
|
|
|
615
625
|
Note that updates will be applied to jobs in nested Flow.
|
|
616
626
|
|
|
@@ -630,6 +640,10 @@ class Flow(MSONable):
|
|
|
630
640
|
dynamic
|
|
631
641
|
The updates will be propagated to Jobs/Flows dynamically generated at
|
|
632
642
|
runtime.
|
|
643
|
+
callback_filter
|
|
644
|
+
A function that takes a Flow or Job instance and returns True if updates
|
|
645
|
+
should be applied to that instance. Allows for custom filtering logic.
|
|
646
|
+
Applies recursively to nested Flows and Jobs so best be specific.
|
|
633
647
|
|
|
634
648
|
Examples
|
|
635
649
|
--------
|
|
@@ -646,16 +660,45 @@ class Flow(MSONable):
|
|
|
646
660
|
The ``metadata`` of both jobs could be updated as follows:
|
|
647
661
|
|
|
648
662
|
>>> flow.update_metadata({"tag": "addition_job"})
|
|
663
|
+
|
|
664
|
+
Or using a callback filter to only update flows containing a specific maker:
|
|
665
|
+
|
|
666
|
+
>>> flow.update_metadata(
|
|
667
|
+
... {"material_id": 42},
|
|
668
|
+
... callback_filter=lambda flow: SomeMaker in map(type, flow)
|
|
669
|
+
... and flow.name == "flow name"
|
|
670
|
+
... )
|
|
649
671
|
"""
|
|
650
|
-
|
|
651
|
-
|
|
672
|
+
from jobflow.utils.dict_mods import apply_mod
|
|
673
|
+
|
|
674
|
+
for job_or_flow in self:
|
|
675
|
+
job_or_flow.update_metadata(
|
|
652
676
|
update,
|
|
653
677
|
name_filter=name_filter,
|
|
654
678
|
function_filter=function_filter,
|
|
655
679
|
dict_mod=dict_mod,
|
|
656
680
|
dynamic=dynamic,
|
|
681
|
+
callback_filter=callback_filter,
|
|
657
682
|
)
|
|
658
683
|
|
|
684
|
+
if callback_filter is not None and callback_filter(self) is False:
|
|
685
|
+
return
|
|
686
|
+
|
|
687
|
+
if dict_mod:
|
|
688
|
+
apply_mod(update, self.metadata)
|
|
689
|
+
else:
|
|
690
|
+
self.metadata.update(update)
|
|
691
|
+
|
|
692
|
+
if dynamic:
|
|
693
|
+
dict_input = {
|
|
694
|
+
"update": update,
|
|
695
|
+
"name_filter": name_filter,
|
|
696
|
+
"function_filter": function_filter,
|
|
697
|
+
"dict_mod": dict_mod,
|
|
698
|
+
"callback_filter": callback_filter,
|
|
699
|
+
}
|
|
700
|
+
self.metadata_updates.append(dict_input)
|
|
701
|
+
|
|
659
702
|
def update_config(
|
|
660
703
|
self,
|
|
661
704
|
config: jobflow.JobConfig | dict,
|
|
@@ -69,17 +69,22 @@ class JobConfig(MSONable):
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
@overload
|
|
72
|
-
def job(method: Callable = None) -> Callable[..., Job]:
|
|
72
|
+
def job(method: Callable | None = None) -> Callable[..., Job]:
|
|
73
73
|
pass
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
@overload
|
|
77
|
-
def job(method: Callable
|
|
77
|
+
def job(method: Callable, **job_kwargs) -> Callable[..., Job]:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@overload
|
|
82
|
+
def job(method: None = None, **job_kwargs) -> Callable[..., Callable[..., Job]]:
|
|
78
83
|
pass
|
|
79
84
|
|
|
80
85
|
|
|
81
86
|
def job(
|
|
82
|
-
method: Callable = None, **job_kwargs
|
|
87
|
+
method: Callable | None = None, **job_kwargs
|
|
83
88
|
) -> Callable[..., Job] | Callable[..., Callable[..., Job]]:
|
|
84
89
|
"""
|
|
85
90
|
Wrap a function to produce a :obj:`Job`.
|
|
@@ -292,12 +297,12 @@ class Job(MSONable):
|
|
|
292
297
|
--------
|
|
293
298
|
Builtin functions such as :obj:`print` can be specified.
|
|
294
299
|
|
|
295
|
-
>>> print_task = Job(function=print,
|
|
300
|
+
>>> print_task = Job(function=print, function_args=("I am a job", ))
|
|
296
301
|
|
|
297
302
|
Or other functions of the Python standard library.
|
|
298
303
|
|
|
299
304
|
>>> import os
|
|
300
|
-
>>> Job(function=os.path.join,
|
|
305
|
+
>>> Job(function=os.path.join, function_args=("folder", "filename.txt"))
|
|
301
306
|
|
|
302
307
|
To use custom functions, the functions should be importable (i.e. not
|
|
303
308
|
defined in another function). For example, if the following function is defined
|
|
@@ -305,7 +310,7 @@ class Job(MSONable):
|
|
|
305
310
|
|
|
306
311
|
>>> def add(a, b):
|
|
307
312
|
... return a + b
|
|
308
|
-
>>> add_job = Job(function=add,
|
|
313
|
+
>>> add_job = Job(function=add, function_args=(1, 2))
|
|
309
314
|
|
|
310
315
|
More details are given in the :obj:`job` decorator docstring.
|
|
311
316
|
|
|
@@ -338,7 +343,6 @@ class Job(MSONable):
|
|
|
338
343
|
function_args = () if function_args is None else function_args
|
|
339
344
|
function_kwargs = {} if function_kwargs is None else function_kwargs
|
|
340
345
|
uuid = suid() if uuid is None else uuid
|
|
341
|
-
metadata = {} if metadata is None else metadata
|
|
342
346
|
config = JobConfig() if config is None else config
|
|
343
347
|
|
|
344
348
|
# make a deep copy of the function (means makers do not share the same instance)
|
|
@@ -349,7 +353,7 @@ class Job(MSONable):
|
|
|
349
353
|
self.uuid = uuid
|
|
350
354
|
self.index = index
|
|
351
355
|
self.name = name
|
|
352
|
-
self.metadata = metadata
|
|
356
|
+
self.metadata = metadata or {}
|
|
353
357
|
self.config = config
|
|
354
358
|
self.hosts = hosts or []
|
|
355
359
|
self.metadata_updates = metadata_updates or []
|
|
@@ -922,6 +926,7 @@ class Job(MSONable):
|
|
|
922
926
|
function_filter: Callable = None,
|
|
923
927
|
dict_mod: bool = False,
|
|
924
928
|
dynamic: bool = True,
|
|
929
|
+
callback_filter: Callable[[jobflow.Flow | Job], bool] | None = None,
|
|
925
930
|
):
|
|
926
931
|
"""
|
|
927
932
|
Update the metadata of the job.
|
|
@@ -945,6 +950,9 @@ class Job(MSONable):
|
|
|
945
950
|
dynamic
|
|
946
951
|
The updates will be propagated to Jobs/Flows dynamically generated at
|
|
947
952
|
runtime.
|
|
953
|
+
callback_filter
|
|
954
|
+
A function that takes a Flow or Job instance and returns True if updates
|
|
955
|
+
should be applied to that instance. Allows for custom filtering logic.
|
|
948
956
|
|
|
949
957
|
Examples
|
|
950
958
|
--------
|
|
@@ -963,11 +971,16 @@ class Job(MSONable):
|
|
|
963
971
|
will not only set the `example` metadata to the `test_job`, but also to all the
|
|
964
972
|
new Jobs that will be generated at runtime by the ExampleMaker.
|
|
965
973
|
|
|
966
|
-
`update_metadata` can be called multiple times with different
|
|
967
|
-
|
|
974
|
+
`update_metadata` can be called multiple times with different filters to control
|
|
975
|
+
which Jobs will be updated. For example, using a callback filter:
|
|
968
976
|
|
|
969
|
-
|
|
970
|
-
|
|
977
|
+
>>> test_job.update_metadata(
|
|
978
|
+
... {"material_id": 42},
|
|
979
|
+
... callback_filter=lambda job: isinstance(job.maker, SomeMaker)
|
|
980
|
+
... )
|
|
981
|
+
|
|
982
|
+
At variance, if `dynamic` is set to `False` the metadata will only be
|
|
983
|
+
added to the filtered Jobs and not to any generated Jobs.
|
|
971
984
|
"""
|
|
972
985
|
from jobflow.utils.dict_mods import apply_mod
|
|
973
986
|
|
|
@@ -977,6 +990,7 @@ class Job(MSONable):
|
|
|
977
990
|
"name_filter": name_filter,
|
|
978
991
|
"function_filter": function_filter,
|
|
979
992
|
"dict_mod": dict_mod,
|
|
993
|
+
"callback_filter": callback_filter,
|
|
980
994
|
}
|
|
981
995
|
self.metadata_updates.append(dict_input)
|
|
982
996
|
|
|
@@ -984,7 +998,6 @@ class Job(MSONable):
|
|
|
984
998
|
function_filter = getattr(function_filter, "__wrapped__", function_filter)
|
|
985
999
|
function = getattr(self.function, "__wrapped__", self.function)
|
|
986
1000
|
|
|
987
|
-
# if function_filter is not None and function_filter != self.function:
|
|
988
1001
|
if function_filter is not None and function_filter != function:
|
|
989
1002
|
return
|
|
990
1003
|
|
|
@@ -993,6 +1006,9 @@ class Job(MSONable):
|
|
|
993
1006
|
):
|
|
994
1007
|
return
|
|
995
1008
|
|
|
1009
|
+
if callback_filter is not None and callback_filter(self) is False:
|
|
1010
|
+
return
|
|
1011
|
+
|
|
996
1012
|
# if we get to here then we pass all the filters
|
|
997
1013
|
if dict_mod:
|
|
998
1014
|
apply_mod(update, self.metadata)
|
|
@@ -1249,7 +1265,7 @@ class Response(typing.Generic[T]):
|
|
|
1249
1265
|
job_returns.output = apply_schema(job_returns.output, output_schema)
|
|
1250
1266
|
|
|
1251
1267
|
job_returns.job_dir = job_dir
|
|
1252
|
-
return cast(Self, job_returns)
|
|
1268
|
+
return cast("Self", job_returns)
|
|
1253
1269
|
|
|
1254
1270
|
if isinstance(job_returns, (list, tuple)):
|
|
1255
1271
|
# check that a Response object is not given as one of many outputs
|
|
@@ -261,7 +261,7 @@ def recursive_call(
|
|
|
261
261
|
|
|
262
262
|
if isinstance(class_filter, Maker):
|
|
263
263
|
# Maker instance supplied rather than a Maker class
|
|
264
|
-
class_filter = class_filter.__class__
|
|
264
|
+
class_filter = class_filter.__class__ # type: ignore[assignment]
|
|
265
265
|
|
|
266
266
|
def _filter(nested_obj: Maker):
|
|
267
267
|
# Filter the Maker object
|
|
@@ -513,7 +513,7 @@ def validate_schema_access(
|
|
|
513
513
|
the bool is ``True`` if the schema access was valid.
|
|
514
514
|
The BaseModel class associated with the item, if any.
|
|
515
515
|
"""
|
|
516
|
-
schema_dict = schema.
|
|
516
|
+
schema_dict = schema.model_json_schema()
|
|
517
517
|
if item not in schema_dict["properties"]:
|
|
518
518
|
raise AttributeError(f"{schema.__name__} does not have attribute '{item}'.")
|
|
519
519
|
|
|
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
|
|
16
16
|
def run_locally(
|
|
17
17
|
flow: jobflow.Flow | jobflow.Job | list[jobflow.Job],
|
|
18
|
-
log: bool = True,
|
|
18
|
+
log: bool | str = True,
|
|
19
19
|
store: jobflow.JobStore | None = None,
|
|
20
20
|
create_folders: bool = False,
|
|
21
21
|
root_dir: str | Path | None = None,
|
|
@@ -30,8 +30,11 @@ def run_locally(
|
|
|
30
30
|
----------
|
|
31
31
|
flow : Flow | Job | list[Job]
|
|
32
32
|
A job or flow.
|
|
33
|
-
log : bool
|
|
34
|
-
|
|
33
|
+
log : bool | str
|
|
34
|
+
Controls logging. Defaults to True. Can be:
|
|
35
|
+
- False: disable logging
|
|
36
|
+
- True: use default logging format (read from ~/.jobflow.yaml)
|
|
37
|
+
- str: custom logging format string (e.g. "%(message)s" for more concise output)
|
|
35
38
|
store : JobStore
|
|
36
39
|
A job store. If a job store is not specified then
|
|
37
40
|
:obj:`JobflowSettings.JOB_STORE` will be used. By default this is a maggma
|
|
@@ -46,7 +49,7 @@ def run_locally(
|
|
|
46
49
|
Raise an error if the flow was not executed successfully.
|
|
47
50
|
allow_external_references : bool
|
|
48
51
|
If False all the references to other outputs should be from other Jobs
|
|
49
|
-
of the Flow.
|
|
52
|
+
of the same Flow.
|
|
50
53
|
raise_immediately : bool
|
|
51
54
|
If True, raise an exception immediately if a job fails. If False, continue
|
|
52
55
|
running the flow and only raise an exception at the end if the flow did not
|
|
@@ -77,7 +80,7 @@ def run_locally(
|
|
|
77
80
|
store.connect()
|
|
78
81
|
|
|
79
82
|
if log:
|
|
80
|
-
initialize_logger()
|
|
83
|
+
initialize_logger(fmt=log if isinstance(log, str) else "")
|
|
81
84
|
|
|
82
85
|
flow = get_flow(flow, allow_external_references=allow_external_references)
|
|
83
86
|
|
|
@@ -11,6 +11,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
11
11
|
from jobflow import JobStore
|
|
12
12
|
|
|
13
13
|
DEFAULT_CONFIG_FILE_PATH = Path("~/.jobflow.yaml").expanduser().as_posix()
|
|
14
|
+
DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)s %(message)s"
|
|
15
|
+
DEFAULT_DIRECTORY_FORMAT = "%Y-%m-%d-%H-%M-%S-%f"
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
def _default_additional_store():
|
|
@@ -28,7 +30,7 @@ class JobflowSettings(BaseSettings):
|
|
|
28
30
|
"""
|
|
29
31
|
Settings for jobflow.
|
|
30
32
|
|
|
31
|
-
The default way to modify these is to
|
|
33
|
+
The default way to modify these is to create a ~/.jobflow.yaml. Alternatively,
|
|
32
34
|
the environment variable ``JOBFLOW_CONFIG_FILE`` can be set to point to a yaml file
|
|
33
35
|
with jobflow settings.
|
|
34
36
|
|
|
@@ -114,9 +116,18 @@ class JobflowSettings(BaseSettings):
|
|
|
114
116
|
"accepted formats.",
|
|
115
117
|
)
|
|
116
118
|
DIRECTORY_FORMAT: str = Field(
|
|
117
|
-
|
|
119
|
+
DEFAULT_DIRECTORY_FORMAT,
|
|
118
120
|
description="Date stamp format used to create directories",
|
|
119
121
|
)
|
|
122
|
+
LOG_FORMAT: str = Field(
|
|
123
|
+
DEFAULT_LOG_FORMAT,
|
|
124
|
+
description="""Logging format string. Common format codes:
|
|
125
|
+
- %(message)s - The logged message
|
|
126
|
+
- %(asctime)s - Human-readable time
|
|
127
|
+
- %(levelname)s - DEBUG, INFO, WARNING, ERROR, or CRITICAL
|
|
128
|
+
- %(name)s - Logger name
|
|
129
|
+
See Python logging documentation for more format codes.""",
|
|
130
|
+
)
|
|
120
131
|
|
|
121
132
|
UID_TYPE: str = Field(
|
|
122
133
|
"uuid4", description="Type of unique identifier to use to track jobs. "
|
|
@@ -170,7 +170,7 @@ def to_pydot(flow: jobflow.Flow):
|
|
|
170
170
|
if isinstance(job, Flow):
|
|
171
171
|
add_cluster(job, cluster)
|
|
172
172
|
else:
|
|
173
|
-
cluster.add_node(pydot_graph.get_node(f
|
|
173
|
+
cluster.add_node(pydot_graph.get_node(f"{job.uuid}")[0])
|
|
174
174
|
|
|
175
175
|
outer_graph.add_subgraph(cluster)
|
|
176
176
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Tools for logging."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def initialize_logger(level: int = logging.INFO, fmt: str = "") -> logging.Logger:
|
|
9
|
+
"""Initialize the default logger.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
level
|
|
14
|
+
The log level.
|
|
15
|
+
fmt
|
|
16
|
+
Custom logging format string. Defaults to JobflowSettings.LOG_FORMAT.
|
|
17
|
+
Common format codes:
|
|
18
|
+
- %(message)s - The logged message
|
|
19
|
+
- %(asctime)s - Human-readable time
|
|
20
|
+
- %(levelname)s - DEBUG, INFO, WARNING, ERROR, or CRITICAL
|
|
21
|
+
See Python logging documentation for more format codes.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
Logger
|
|
26
|
+
A logging instance with customized formatter and handlers.
|
|
27
|
+
"""
|
|
28
|
+
import sys
|
|
29
|
+
|
|
30
|
+
from jobflow import SETTINGS
|
|
31
|
+
|
|
32
|
+
log = logging.getLogger("jobflow")
|
|
33
|
+
log.setLevel(level)
|
|
34
|
+
log.handlers = [] # reset logging handlers if they already exist
|
|
35
|
+
|
|
36
|
+
formatter = logging.Formatter(fmt or SETTINGS.LOG_FORMAT)
|
|
37
|
+
|
|
38
|
+
screen_handler = logging.StreamHandler(stream=sys.stdout)
|
|
39
|
+
screen_handler.setFormatter(formatter)
|
|
40
|
+
log.addHandler(screen_handler)
|
|
41
|
+
return log
|
|
@@ -4,7 +4,10 @@ from monty.dev import deprecated
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
@deprecated(
|
|
7
|
-
message=
|
|
7
|
+
message=(
|
|
8
|
+
"The UUID system will be replaced with a UID that "
|
|
9
|
+
"contains both the UUID and ULID."
|
|
10
|
+
)
|
|
8
11
|
)
|
|
9
12
|
def suuid() -> str:
|
|
10
13
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: jobflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: jobflow is a library for writing computational workflows
|
|
5
5
|
Author-email: Alex Ganose <a.ganose@imperial.ac.uk>
|
|
6
6
|
License: modified BSD
|
|
@@ -17,11 +17,12 @@ Classifier: Operating System :: OS Independent
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
22
|
Classifier: Topic :: Database :: Front-Ends
|
|
22
23
|
Classifier: Topic :: Other/Nonlisted Topic
|
|
23
24
|
Classifier: Topic :: Scientific/Engineering
|
|
24
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.10
|
|
25
26
|
Description-Content-Type: text/markdown
|
|
26
27
|
License-File: LICENSE
|
|
27
28
|
Requires-Dist: PyYAML
|
|
@@ -34,39 +35,40 @@ Requires-Dist: pydash
|
|
|
34
35
|
Provides-Extra: ulid
|
|
35
36
|
Requires-Dist: python-ulid; extra == "ulid"
|
|
36
37
|
Provides-Extra: docs
|
|
37
|
-
Requires-Dist: autodoc_pydantic==2.
|
|
38
|
-
Requires-Dist: furo==2024.
|
|
39
|
-
Requires-Dist: ipython==
|
|
40
|
-
Requires-Dist: myst_parser==
|
|
41
|
-
Requires-Dist: nbsphinx==0.9.
|
|
38
|
+
Requires-Dist: autodoc_pydantic==2.2.0; extra == "docs"
|
|
39
|
+
Requires-Dist: furo==2024.8.6; extra == "docs"
|
|
40
|
+
Requires-Dist: ipython==9.3.0; extra == "docs"
|
|
41
|
+
Requires-Dist: myst_parser==4.0.1; extra == "docs"
|
|
42
|
+
Requires-Dist: nbsphinx==0.9.7; extra == "docs"
|
|
42
43
|
Requires-Dist: sphinx-copybutton==0.5.2; extra == "docs"
|
|
43
|
-
Requires-Dist: sphinx==
|
|
44
|
+
Requires-Dist: sphinx==8.1.3; extra == "docs"
|
|
44
45
|
Provides-Extra: dev
|
|
45
46
|
Requires-Dist: pre-commit>=2.12.1; extra == "dev"
|
|
46
47
|
Requires-Dist: typing_extensions; python_version < "3.11" and extra == "dev"
|
|
47
48
|
Provides-Extra: tests
|
|
48
|
-
Requires-Dist: moto==
|
|
49
|
-
Requires-Dist: pytest-cov==
|
|
50
|
-
Requires-Dist: pytest==8.
|
|
49
|
+
Requires-Dist: moto==5.1.5; extra == "tests"
|
|
50
|
+
Requires-Dist: pytest-cov==6.1.1; extra == "tests"
|
|
51
|
+
Requires-Dist: pytest==8.4.0; extra == "tests"
|
|
51
52
|
Provides-Extra: vis
|
|
52
53
|
Requires-Dist: matplotlib; extra == "vis"
|
|
53
54
|
Requires-Dist: pydot; extra == "vis"
|
|
54
55
|
Provides-Extra: fireworks
|
|
55
56
|
Requires-Dist: FireWorks; extra == "fireworks"
|
|
56
57
|
Provides-Extra: strict
|
|
57
|
-
Requires-Dist: FireWorks==2.0.
|
|
58
|
-
Requires-Dist: PyYAML==6.0.
|
|
59
|
-
Requires-Dist: maggma==0.
|
|
60
|
-
Requires-Dist: matplotlib==3.
|
|
61
|
-
Requires-Dist: monty==
|
|
62
|
-
Requires-Dist: moto==
|
|
63
|
-
Requires-Dist: networkx==3.2
|
|
64
|
-
Requires-Dist: pydantic-settings==2.
|
|
65
|
-
Requires-Dist: pydantic==2.
|
|
66
|
-
Requires-Dist: pydash==8.0.
|
|
67
|
-
Requires-Dist: pydot==
|
|
68
|
-
Requires-Dist: python-ulid==
|
|
69
|
-
Requires-Dist: typing-extensions==4.
|
|
58
|
+
Requires-Dist: FireWorks==2.0.4; extra == "strict"
|
|
59
|
+
Requires-Dist: PyYAML==6.0.2; extra == "strict"
|
|
60
|
+
Requires-Dist: maggma==0.71.5; extra == "strict"
|
|
61
|
+
Requires-Dist: matplotlib==3.10.3; extra == "strict"
|
|
62
|
+
Requires-Dist: monty==2025.3.3; extra == "strict"
|
|
63
|
+
Requires-Dist: moto==5.1.5; extra == "strict"
|
|
64
|
+
Requires-Dist: networkx==3.4.2; extra == "strict"
|
|
65
|
+
Requires-Dist: pydantic-settings==2.9.1; extra == "strict"
|
|
66
|
+
Requires-Dist: pydantic==2.11.5; extra == "strict"
|
|
67
|
+
Requires-Dist: pydash==8.0.5; extra == "strict"
|
|
68
|
+
Requires-Dist: pydot==4.0.0; extra == "strict"
|
|
69
|
+
Requires-Dist: python-ulid==3.0.0; extra == "strict"
|
|
70
|
+
Requires-Dist: typing-extensions==4.13.2; extra == "strict"
|
|
71
|
+
Dynamic: license-file
|
|
70
72
|
|
|
71
73
|
<div align="center">
|
|
72
74
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
PyYAML
|
|
2
|
+
maggma>=0.57.0
|
|
3
|
+
monty>=2023.9.25
|
|
4
|
+
networkx
|
|
5
|
+
pydantic-settings>=2.0.3
|
|
6
|
+
pydantic>=2.0.1
|
|
7
|
+
pydash
|
|
8
|
+
|
|
9
|
+
[dev]
|
|
10
|
+
pre-commit>=2.12.1
|
|
11
|
+
|
|
12
|
+
[dev:python_version < "3.11"]
|
|
13
|
+
typing_extensions
|
|
14
|
+
|
|
15
|
+
[docs]
|
|
16
|
+
autodoc_pydantic==2.2.0
|
|
17
|
+
furo==2024.8.6
|
|
18
|
+
ipython==9.3.0
|
|
19
|
+
myst_parser==4.0.1
|
|
20
|
+
nbsphinx==0.9.7
|
|
21
|
+
sphinx-copybutton==0.5.2
|
|
22
|
+
sphinx==8.1.3
|
|
23
|
+
|
|
24
|
+
[fireworks]
|
|
25
|
+
FireWorks
|
|
26
|
+
|
|
27
|
+
[strict]
|
|
28
|
+
FireWorks==2.0.4
|
|
29
|
+
PyYAML==6.0.2
|
|
30
|
+
maggma==0.71.5
|
|
31
|
+
matplotlib==3.10.3
|
|
32
|
+
monty==2025.3.3
|
|
33
|
+
moto==5.1.5
|
|
34
|
+
networkx==3.4.2
|
|
35
|
+
pydantic-settings==2.9.1
|
|
36
|
+
pydantic==2.11.5
|
|
37
|
+
pydash==8.0.5
|
|
38
|
+
pydot==4.0.0
|
|
39
|
+
python-ulid==3.0.0
|
|
40
|
+
typing-extensions==4.13.2
|
|
41
|
+
|
|
42
|
+
[tests]
|
|
43
|
+
moto==5.1.5
|
|
44
|
+
pytest-cov==6.1.1
|
|
45
|
+
pytest==8.4.0
|
|
46
|
+
|
|
47
|
+
[ulid]
|
|
48
|
+
python-ulid
|
|
49
|
+
|
|
50
|
+
[vis]
|
|
51
|
+
matplotlib
|
|
52
|
+
pydot
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"""Tools for logging."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def initialize_logger(level: int = logging.INFO) -> logging.Logger:
|
|
7
|
-
"""Initialize the default logger.
|
|
8
|
-
|
|
9
|
-
Parameters
|
|
10
|
-
----------
|
|
11
|
-
level
|
|
12
|
-
The log level.
|
|
13
|
-
|
|
14
|
-
Returns
|
|
15
|
-
-------
|
|
16
|
-
Logger
|
|
17
|
-
A logging instance with customized formatter and handlers.
|
|
18
|
-
"""
|
|
19
|
-
import sys
|
|
20
|
-
|
|
21
|
-
log = logging.getLogger("jobflow")
|
|
22
|
-
log.setLevel(level)
|
|
23
|
-
log.handlers = [] # reset logging handlers if they already exist
|
|
24
|
-
|
|
25
|
-
fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
|
26
|
-
screen_handler = logging.StreamHandler(stream=sys.stdout)
|
|
27
|
-
screen_handler.setFormatter(fmt)
|
|
28
|
-
log.addHandler(screen_handler)
|
|
29
|
-
return log
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
PyYAML
|
|
2
|
-
maggma>=0.57.0
|
|
3
|
-
monty>=2023.9.25
|
|
4
|
-
networkx
|
|
5
|
-
pydantic-settings>=2.0.3
|
|
6
|
-
pydantic>=2.0.1
|
|
7
|
-
pydash
|
|
8
|
-
|
|
9
|
-
[dev]
|
|
10
|
-
pre-commit>=2.12.1
|
|
11
|
-
|
|
12
|
-
[dev:python_version < "3.11"]
|
|
13
|
-
typing_extensions
|
|
14
|
-
|
|
15
|
-
[docs]
|
|
16
|
-
autodoc_pydantic==2.1.0
|
|
17
|
-
furo==2024.5.6
|
|
18
|
-
ipython==8.26.0
|
|
19
|
-
myst_parser==3.0.1
|
|
20
|
-
nbsphinx==0.9.4
|
|
21
|
-
sphinx-copybutton==0.5.2
|
|
22
|
-
sphinx==7.4.4
|
|
23
|
-
|
|
24
|
-
[fireworks]
|
|
25
|
-
FireWorks
|
|
26
|
-
|
|
27
|
-
[strict]
|
|
28
|
-
FireWorks==2.0.3
|
|
29
|
-
PyYAML==6.0.1
|
|
30
|
-
maggma==0.69.0
|
|
31
|
-
matplotlib==3.9.1
|
|
32
|
-
monty==2024.7.12
|
|
33
|
-
moto==4.2.13
|
|
34
|
-
networkx==3.2.1
|
|
35
|
-
pydantic-settings==2.3.4
|
|
36
|
-
pydantic==2.8.2
|
|
37
|
-
pydash==8.0.1
|
|
38
|
-
pydot==2.0.0
|
|
39
|
-
python-ulid==2.7.0
|
|
40
|
-
typing-extensions==4.12.2
|
|
41
|
-
|
|
42
|
-
[tests]
|
|
43
|
-
moto==4.2.13
|
|
44
|
-
pytest-cov==5.0.0
|
|
45
|
-
pytest==8.2.2
|
|
46
|
-
|
|
47
|
-
[ulid]
|
|
48
|
-
python-ulid
|
|
49
|
-
|
|
50
|
-
[vis]
|
|
51
|
-
matplotlib
|
|
52
|
-
pydot
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|