jobflow 0.1.17__tar.gz → 0.1.19__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 (37) hide show
  1. {jobflow-0.1.17/src/jobflow.egg-info → jobflow-0.1.19}/PKG-INFO +28 -21
  2. {jobflow-0.1.17 → jobflow-0.1.19}/README.md +9 -3
  3. {jobflow-0.1.17 → jobflow-0.1.19}/pyproject.toml +24 -20
  4. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/core/flow.py +51 -9
  5. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/core/job.py +71 -21
  6. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/core/maker.py +2 -4
  7. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/core/reference.py +1 -1
  8. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/core/store.py +5 -4
  9. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/managers/fireworks.py +15 -0
  10. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/managers/local.py +13 -8
  11. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/settings.py +13 -2
  12. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/utils/__init__.py +1 -0
  13. jobflow-0.1.19/src/jobflow/utils/log.py +41 -0
  14. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/utils/uid.py +2 -1
  15. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/utils/uuid.py +1 -0
  16. {jobflow-0.1.17 → jobflow-0.1.19/src/jobflow.egg-info}/PKG-INFO +28 -21
  17. jobflow-0.1.19/src/jobflow.egg-info/requires.txt +52 -0
  18. jobflow-0.1.17/src/jobflow/utils/log.py +0 -29
  19. jobflow-0.1.17/src/jobflow.egg-info/requires.txt +0 -49
  20. {jobflow-0.1.17 → jobflow-0.1.19}/LICENSE +0 -0
  21. {jobflow-0.1.17 → jobflow-0.1.19}/setup.cfg +0 -0
  22. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/__init__.py +0 -0
  23. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/_version.py +0 -0
  24. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/core/__init__.py +0 -0
  25. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/core/schemas.py +0 -0
  26. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/core/state.py +0 -0
  27. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/managers/__init__.py +0 -0
  28. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/py.typed +0 -0
  29. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/utils/dict_mods.py +0 -0
  30. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/utils/enum.py +0 -0
  31. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/utils/find.py +0 -0
  32. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow/utils/graph.py +0 -0
  33. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow.egg-info/SOURCES.txt +0 -0
  34. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow.egg-info/dependency_links.txt +0 -0
  35. {jobflow-0.1.17 → jobflow-0.1.19}/src/jobflow.egg-info/top_level.txt +0 -0
  36. {jobflow-0.1.17 → jobflow-0.1.19}/tests/test_settings.py +0 -0
  37. {jobflow-0.1.17 → jobflow-0.1.19}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jobflow
3
- Version: 0.1.17
3
+ Version: 0.1.19
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
@@ -34,19 +34,20 @@ Requires-Dist: pydash
34
34
  Provides-Extra: ulid
35
35
  Requires-Dist: python-ulid; extra == "ulid"
36
36
  Provides-Extra: docs
37
- Requires-Dist: autodoc_pydantic==2.0.1; extra == "docs"
38
- Requires-Dist: furo==2023.9.10; extra == "docs"
39
- Requires-Dist: ipython==8.20.0; extra == "docs"
40
- Requires-Dist: myst_parser==2.0.0; extra == "docs"
41
- Requires-Dist: nbsphinx==0.9.3; extra == "docs"
37
+ Requires-Dist: autodoc_pydantic==2.1.0; extra == "docs"
38
+ Requires-Dist: furo==2024.8.6; extra == "docs"
39
+ Requires-Dist: ipython==8.29.0; extra == "docs"
40
+ Requires-Dist: myst_parser==4.0.0; extra == "docs"
41
+ Requires-Dist: nbsphinx==0.9.5; extra == "docs"
42
42
  Requires-Dist: sphinx-copybutton==0.5.2; extra == "docs"
43
- Requires-Dist: sphinx==7.2.6; extra == "docs"
43
+ Requires-Dist: sphinx==8.1.3; extra == "docs"
44
44
  Provides-Extra: dev
45
45
  Requires-Dist: pre-commit>=2.12.1; extra == "dev"
46
+ Requires-Dist: typing_extensions; python_version < "3.11" and extra == "dev"
46
47
  Provides-Extra: tests
47
48
  Requires-Dist: moto==4.2.13; extra == "tests"
48
- Requires-Dist: pytest-cov==4.1.0; extra == "tests"
49
- Requires-Dist: pytest==7.4.4; extra == "tests"
49
+ Requires-Dist: pytest-cov==6.0.0; extra == "tests"
50
+ Requires-Dist: pytest==8.3.3; extra == "tests"
50
51
  Provides-Extra: vis
51
52
  Requires-Dist: matplotlib; extra == "vis"
52
53
  Requires-Dist: pydot; extra == "vis"
@@ -54,20 +55,22 @@ Provides-Extra: fireworks
54
55
  Requires-Dist: FireWorks; extra == "fireworks"
55
56
  Provides-Extra: strict
56
57
  Requires-Dist: FireWorks==2.0.3; extra == "strict"
57
- Requires-Dist: PyYAML==6.0.1; extra == "strict"
58
- Requires-Dist: maggma==0.61.0; extra == "strict"
59
- Requires-Dist: matplotlib==3.8.2; extra == "strict"
60
- Requires-Dist: monty==2023.11.3; extra == "strict"
58
+ Requires-Dist: PyYAML==6.0.2; extra == "strict"
59
+ Requires-Dist: maggma==0.70.0; extra == "strict"
60
+ Requires-Dist: matplotlib==3.9.2; extra == "strict"
61
+ Requires-Dist: monty==2024.10.21; extra == "strict"
61
62
  Requires-Dist: moto==4.2.13; extra == "strict"
62
63
  Requires-Dist: networkx==3.2.1; extra == "strict"
63
- Requires-Dist: pydantic-settings==2.1.0; extra == "strict"
64
- Requires-Dist: pydantic==2.5.3; extra == "strict"
65
- Requires-Dist: pydash==7.0.6; extra == "strict"
64
+ Requires-Dist: pydantic-settings==2.6.1; extra == "strict"
65
+ Requires-Dist: pydantic==2.9.2; extra == "strict"
66
+ Requires-Dist: pydash==8.0.4; extra == "strict"
66
67
  Requires-Dist: pydot==2.0.0; extra == "strict"
67
- Requires-Dist: typing-extensions==4.9.0; extra == "strict"
68
- Requires-Dist: python-ulid==2.2.0; extra == "strict"
68
+ Requires-Dist: python-ulid==3.0.0; extra == "strict"
69
+ Requires-Dist: typing-extensions==4.12.2; extra == "strict"
69
70
 
70
- # jobflow
71
+ <div align="center">
72
+
73
+ # ![Jobflow](docs/_static/img/jobflow_logo.svg)
71
74
 
72
75
  [![tests](https://img.shields.io/github/actions/workflow/status/materialsproject/jobflow/testing.yml?branch=main&label=tests)](https://github.com/materialsproject/jobflow/actions?query=workflow%3Atesting)
73
76
  [![code coverage](https://img.shields.io/codecov/c/gh/materialsproject/jobflow/main)](https://codecov.io/gh/materialsproject/jobflow/)
@@ -75,11 +78,14 @@ Requires-Dist: python-ulid==2.2.0; extra == "strict"
75
78
  ![supported python versions](https://img.shields.io/pypi/pyversions/jobflow)
76
79
  [![DOI](https://joss.theoj.org/papers/10.21105/joss.05995/status.svg)](https://doi.org/10.21105/joss.05995)
77
80
 
81
+ </div>
82
+
78
83
  [Documentation](https://materialsproject.github.io/jobflow/) | [PyPI](https://pypi.org/project/jobflow/) | [GitHub](https://github.com/materialsproject/jobflow) | [Paper](https://doi.org/10.21105/joss.05995)
79
84
 
80
85
  Jobflow is a free, open-source library for writing and executing workflows. Complex
81
86
  workflows can be defined using simple python functions and executed locally or on
82
- arbitrary computing resources using the [FireWorks][fireworks] workflow manager.
87
+ arbitrary computing resources using the [jobflow-remote][jfr] or [FireWorks][fireworks]
88
+ workflow managers.
83
89
 
84
90
  Some features that distinguish jobflow are dynamic workflows, easy compositing and
85
91
  connecting of workflows, and the ability to store workflow outputs across multiple
@@ -98,7 +104,7 @@ Some of its features include:
98
104
  way to build complex workflows.
99
105
  - Integration with multiple databases (MongoDB, S3, GridFS, and more) through the
100
106
  [Maggma][maggma] package.
101
- - Support for the [FireWorks][fireworks] workflow management system, allowing workflow
107
+ - Support for the [jobflow-remote][jfr] and [FireWorks][fireworks] workflow management systems, allowing workflow
102
108
  execution on multicore machines or through a queue, on a single machine or multiple
103
109
  machines.
104
110
  - Support for dynamic workflows — workflows that modify themselves or create new ones
@@ -192,6 +198,7 @@ Jobflow was designed by Alex Ganose, Anubhav Jain, Gian-Marco Rignanese, David W
192
198
 
193
199
  [maggma]: https://materialsproject.github.io/maggma/
194
200
  [fireworks]: https://materialsproject.github.io/fireworks/
201
+ [jfr]: https://matgenix.github.io/jobflow-remote
195
202
  [help-forum]: https://matsci.org/c/fireworks
196
203
  [issues]: https://github.com/materialsproject/jobflow/issues
197
204
  [changelog]: https://materialsproject.github.io/jobflow/changelog.html
@@ -1,4 +1,6 @@
1
- # jobflow
1
+ <div align="center">
2
+
3
+ # ![Jobflow](docs/_static/img/jobflow_logo.svg)
2
4
 
3
5
  [![tests](https://img.shields.io/github/actions/workflow/status/materialsproject/jobflow/testing.yml?branch=main&label=tests)](https://github.com/materialsproject/jobflow/actions?query=workflow%3Atesting)
4
6
  [![code coverage](https://img.shields.io/codecov/c/gh/materialsproject/jobflow/main)](https://codecov.io/gh/materialsproject/jobflow/)
@@ -6,11 +8,14 @@
6
8
  ![supported python versions](https://img.shields.io/pypi/pyversions/jobflow)
7
9
  [![DOI](https://joss.theoj.org/papers/10.21105/joss.05995/status.svg)](https://doi.org/10.21105/joss.05995)
8
10
 
11
+ </div>
12
+
9
13
  [Documentation](https://materialsproject.github.io/jobflow/) | [PyPI](https://pypi.org/project/jobflow/) | [GitHub](https://github.com/materialsproject/jobflow) | [Paper](https://doi.org/10.21105/joss.05995)
10
14
 
11
15
  Jobflow is a free, open-source library for writing and executing workflows. Complex
12
16
  workflows can be defined using simple python functions and executed locally or on
13
- arbitrary computing resources using the [FireWorks][fireworks] workflow manager.
17
+ arbitrary computing resources using the [jobflow-remote][jfr] or [FireWorks][fireworks]
18
+ workflow managers.
14
19
 
15
20
  Some features that distinguish jobflow are dynamic workflows, easy compositing and
16
21
  connecting of workflows, and the ability to store workflow outputs across multiple
@@ -29,7 +34,7 @@ Some of its features include:
29
34
  way to build complex workflows.
30
35
  - Integration with multiple databases (MongoDB, S3, GridFS, and more) through the
31
36
  [Maggma][maggma] package.
32
- - Support for the [FireWorks][fireworks] workflow management system, allowing workflow
37
+ - Support for the [jobflow-remote][jfr] and [FireWorks][fireworks] workflow management systems, allowing workflow
33
38
  execution on multicore machines or through a queue, on a single machine or multiple
34
39
  machines.
35
40
  - Support for dynamic workflows — workflows that modify themselves or create new ones
@@ -123,6 +128,7 @@ Jobflow was designed by Alex Ganose, Anubhav Jain, Gian-Marco Rignanese, David W
123
128
 
124
129
  [maggma]: https://materialsproject.github.io/maggma/
125
130
  [fireworks]: https://materialsproject.github.io/fireworks/
131
+ [jfr]: https://matgenix.github.io/jobflow-remote
126
132
  [help-forum]: https://matsci.org/c/fireworks
127
133
  [issues]: https://github.com/materialsproject/jobflow/issues
128
134
  [changelog]: https://materialsproject.github.io/jobflow/changelog.html
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["setuptools >= 42", "versioningit ~= 1.0", "wheel"]
2
+ requires = ["setuptools >= 42", "versioningit >= 1,< 4", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
@@ -38,32 +38,32 @@ dependencies = [
38
38
  [project.optional-dependencies]
39
39
  ulid = ["python-ulid"]
40
40
  docs = [
41
- "autodoc_pydantic==2.0.1",
42
- "furo==2023.9.10",
43
- "ipython==8.20.0",
44
- "myst_parser==2.0.0",
45
- "nbsphinx==0.9.3",
41
+ "autodoc_pydantic==2.1.0",
42
+ "furo==2024.8.6",
43
+ "ipython==8.29.0",
44
+ "myst_parser==4.0.0",
45
+ "nbsphinx==0.9.5",
46
46
  "sphinx-copybutton==0.5.2",
47
- "sphinx==7.2.6",
47
+ "sphinx==8.1.3",
48
48
  ]
49
- dev = ["pre-commit>=2.12.1"]
50
- tests = ["moto==4.2.13", "pytest-cov==4.1.0", "pytest==7.4.4"]
49
+ dev = ["pre-commit>=2.12.1", "typing_extensions; python_version < '3.11'"]
50
+ tests = ["moto==4.2.13", "pytest-cov==6.0.0", "pytest==8.3.3"]
51
51
  vis = ["matplotlib", "pydot"]
52
52
  fireworks = ["FireWorks"]
53
53
  strict = [
54
54
  "FireWorks==2.0.3",
55
- "PyYAML==6.0.1",
56
- "maggma==0.61.0",
57
- "matplotlib==3.8.2",
58
- "monty==2023.11.3",
55
+ "PyYAML==6.0.2",
56
+ "maggma==0.70.0",
57
+ "matplotlib==3.9.2",
58
+ "monty==2024.10.21",
59
59
  "moto==4.2.13",
60
60
  "networkx==3.2.1",
61
- "pydantic-settings==2.1.0",
62
- "pydantic==2.5.3",
63
- "pydash==7.0.6",
61
+ "pydantic-settings==2.6.1",
62
+ "pydantic==2.9.2",
63
+ "pydash==8.0.4",
64
64
  "pydot==2.0.0",
65
- "typing-extensions==4.9.0",
66
- "python-ulid==2.2.0",
65
+ "python-ulid==3.0.0",
66
+ "typing-extensions==4.12.2",
67
67
  ]
68
68
 
69
69
  [project.urls]
@@ -121,7 +121,9 @@ exclude_lines = [
121
121
 
122
122
  [tool.ruff]
123
123
  target-version = "py39"
124
- ignore-init-module-imports = true
124
+ output-format = "concise"
125
+
126
+ [tool.ruff.lint]
125
127
  select = [
126
128
  "B", # flake8-bugbear
127
129
  "C4", # flake8-comprehensions
@@ -159,6 +161,7 @@ ignore = [
159
161
  "DTZ005",
160
162
  "FBT001",
161
163
  "FBT002",
164
+ "ISC001",
162
165
  "PLR0911", # too-many-return-statements
163
166
  "PLR0912", # too-many-branches
164
167
  "PLR0913", # too-many-arguments
@@ -170,10 +173,11 @@ ignore = [
170
173
  pydocstyle.convention = "numpy"
171
174
  isort.known-first-party = ["jobflow"]
172
175
 
173
- [tool.ruff.per-file-ignores]
176
+ [tool.ruff.lint.per-file-ignores]
174
177
  # F401: unused import
175
178
  "__init__.py" = ["F401"]
176
179
  # D: pydocstyle
177
180
  # PLR2004: magic-value-comparison
178
181
  # PT004: pytest-missing-fixture-name-underscore
179
182
  "**/tests/*" = ["ANN", "ARG001", "D", "PLR2004", "PT004", "S101"]
183
+ "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)
@@ -158,9 +167,8 @@ class Flow(MSONable):
158
167
  self, idx: int | slice, value: Flow | Job | Sequence[Flow | Job]
159
168
  ) -> None:
160
169
  """Set the job(s) or subflow(s) at the given index/slice."""
161
- if (
162
- not isinstance(value, (Flow, jobflow.Job, tuple, list))
163
- or isinstance(value, (tuple, list))
170
+ if not isinstance(value, (Flow, jobflow.Job, tuple, list)) or (
171
+ isinstance(value, (tuple, list))
164
172
  and not all(isinstance(v, (Flow, jobflow.Job)) for v in value)
165
173
  ):
166
174
  raise TypeError(
@@ -191,7 +199,7 @@ class Flow(MSONable):
191
199
  if other not in self:
192
200
  raise ValueError(f"{other!r} not found in flow")
193
201
  new_flow = deepcopy(self)
194
- new_flow.jobs = tuple([job for job in new_flow if job != other])
202
+ new_flow.jobs = tuple(job for job in new_flow if job != other)
195
203
  return new_flow
196
204
 
197
205
  def __repr__(self, level: int = 0, prefix: str = "") -> str:
@@ -583,7 +591,7 @@ class Flow(MSONable):
583
591
  dict_mod=dict_mod,
584
592
  )
585
593
 
586
- def append_name(self, append_str: str, prepend: bool = False):
594
+ def append_name(self, append_str: str, prepend: bool = False, dynamic: bool = True):
587
595
  """
588
596
  Append a string to the name of the flow and all jobs contained in it.
589
597
 
@@ -600,7 +608,7 @@ class Flow(MSONable):
600
608
  self.name += append_str
601
609
 
602
610
  for job in self:
603
- job.append_name(append_str, prepend=prepend)
611
+ job.append_name(append_str, prepend=prepend, dynamic=dynamic)
604
612
 
605
613
  def update_metadata(
606
614
  self,
@@ -609,9 +617,10 @@ class Flow(MSONable):
609
617
  function_filter: Callable = None,
610
618
  dict_mod: bool = False,
611
619
  dynamic: bool = True,
620
+ callback_filter: Callable[[Flow | Job], bool] = lambda _: True,
612
621
  ):
613
622
  """
614
- Update the metadata of all Jobs in the Flow.
623
+ Update the metadata of the Flow and/or its Jobs.
615
624
 
616
625
  Note that updates will be applied to jobs in nested Flow.
617
626
 
@@ -631,6 +640,10 @@ class Flow(MSONable):
631
640
  dynamic
632
641
  The updates will be propagated to Jobs/Flows dynamically generated at
633
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.
634
647
 
635
648
  Examples
636
649
  --------
@@ -647,16 +660,45 @@ class Flow(MSONable):
647
660
  The ``metadata`` of both jobs could be updated as follows:
648
661
 
649
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
+ ... )
650
671
  """
651
- for job in self:
652
- job.update_metadata(
672
+ from jobflow.utils.dict_mods import apply_mod
673
+
674
+ for job_or_flow in self:
675
+ job_or_flow.update_metadata(
653
676
  update,
654
677
  name_filter=name_filter,
655
678
  function_filter=function_filter,
656
679
  dict_mod=dict_mod,
657
680
  dynamic=dynamic,
681
+ callback_filter=callback_filter,
658
682
  )
659
683
 
684
+ if 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
+
660
702
  def update_config(
661
703
  self,
662
704
  config: jobflow.JobConfig | dict,
@@ -6,14 +6,17 @@ import logging
6
6
  import typing
7
7
  import warnings
8
8
  from dataclasses import dataclass, field
9
+ from typing import cast, overload
9
10
 
10
11
  from monty.json import MSONable, jsanitize
12
+ from typing_extensions import Self
11
13
 
12
14
  from jobflow.core.reference import OnMissing, OutputReference
13
15
  from jobflow.utils.uid import suid
14
16
 
15
17
  if typing.TYPE_CHECKING:
16
18
  from collections.abc import Hashable, Sequence
19
+ from pathlib import Path
17
20
  from typing import Any, Callable
18
21
 
19
22
  from networkx import DiGraph
@@ -65,7 +68,24 @@ class JobConfig(MSONable):
65
68
  response_manager_config: dict = field(default_factory=dict)
66
69
 
67
70
 
68
- def job(method: Callable = None, **job_kwargs):
71
+ @overload
72
+ def job(method: Callable | None = None) -> Callable[..., Job]:
73
+ pass
74
+
75
+
76
+ @overload
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]]:
83
+ pass
84
+
85
+
86
+ def job(
87
+ method: Callable | None = None, **job_kwargs
88
+ ) -> Callable[..., Job] | Callable[..., Callable[..., Job]]:
69
89
  """
70
90
  Wrap a function to produce a :obj:`Job`.
71
91
 
@@ -277,12 +297,12 @@ class Job(MSONable):
277
297
  --------
278
298
  Builtin functions such as :obj:`print` can be specified.
279
299
 
280
- >>> print_task = Job(function=print, args=("I am a job", ))
300
+ >>> print_task = Job(function=print, function_args=("I am a job", ))
281
301
 
282
302
  Or other functions of the Python standard library.
283
303
 
284
304
  >>> import os
285
- >>> Job(function=os.path.join, args=("folder", "filename.txt"))
305
+ >>> Job(function=os.path.join, function_args=("folder", "filename.txt"))
286
306
 
287
307
  To use custom functions, the functions should be importable (i.e. not
288
308
  defined in another function). For example, if the following function is defined
@@ -290,7 +310,7 @@ class Job(MSONable):
290
310
 
291
311
  >>> def add(a, b):
292
312
  ... return a + b
293
- >>> add_job = Job(function=add, args=(1, 2))
313
+ >>> add_job = Job(function=add, function_args=(1, 2))
294
314
 
295
315
  More details are given in the :obj:`job` decorator docstring.
296
316
 
@@ -313,6 +333,7 @@ class Job(MSONable):
313
333
  hosts: list[str] = None,
314
334
  metadata_updates: list[dict[str, Any]] = None,
315
335
  config_updates: list[dict[str, Any]] = None,
336
+ name_updates: list[dict[str, Any]] = None,
316
337
  **kwargs,
317
338
  ):
318
339
  from copy import deepcopy
@@ -322,7 +343,6 @@ class Job(MSONable):
322
343
  function_args = () if function_args is None else function_args
323
344
  function_kwargs = {} if function_kwargs is None else function_kwargs
324
345
  uuid = suid() if uuid is None else uuid
325
- metadata = {} if metadata is None else metadata
326
346
  config = JobConfig() if config is None else config
327
347
 
328
348
  # make a deep copy of the function (means makers do not share the same instance)
@@ -333,10 +353,11 @@ class Job(MSONable):
333
353
  self.uuid = uuid
334
354
  self.index = index
335
355
  self.name = name
336
- self.metadata = metadata
356
+ self.metadata = metadata or {}
337
357
  self.config = config
338
358
  self.hosts = hosts or []
339
359
  self.metadata_updates = metadata_updates or []
360
+ self.name_updates = name_updates or []
340
361
  self.config_updates = config_updates or []
341
362
  self._kwargs = kwargs
342
363
 
@@ -526,7 +547,7 @@ class Job(MSONable):
526
547
  self.uuid = uuid
527
548
  self.output = self.output.set_uuid(uuid)
528
549
 
529
- def run(self, store: jobflow.JobStore) -> Response:
550
+ def run(self, store: jobflow.JobStore, job_dir: Path = None) -> Response:
530
551
  """
531
552
  Run the job.
532
553
 
@@ -581,7 +602,9 @@ class Job(MSONable):
581
602
  function = types.MethodType(function, bound)
582
603
 
583
604
  response = function(*self.function_args, **self.function_kwargs)
584
- response = Response.from_job_returns(response, self.output_schema)
605
+ response = Response.from_job_returns(
606
+ response, self.output_schema, job_dir=job_dir
607
+ )
585
608
 
586
609
  if response.replace is not None:
587
610
  response.replace = prepare_replace(response.replace, self)
@@ -604,6 +627,8 @@ class Job(MSONable):
604
627
  new_jobs.update_metadata(**metadata_update, dynamic=True)
605
628
  for config_update in self.config_updates:
606
629
  new_jobs.update_config(**config_update, dynamic=True)
630
+ for name_update in self.name_updates:
631
+ new_jobs.append_name(**name_update, dynamic=True)
607
632
 
608
633
  if self.config.response_manager_config:
609
634
  passed_config = self.config.response_manager_config
@@ -872,7 +897,7 @@ class Job(MSONable):
872
897
  dict_mod=dict_mod,
873
898
  )
874
899
 
875
- def append_name(self, append_str: str, prepend: bool = False):
900
+ def append_name(self, append_str: str, prepend: bool = False, dynamic: bool = True):
876
901
  """
877
902
  Append a string to the name of the job.
878
903
 
@@ -882,12 +907,18 @@ class Job(MSONable):
882
907
  A string to append.
883
908
  prepend
884
909
  Prepend the name rather than appending it.
910
+ dynamic
911
+ The updates will be propagated to Jobs/Flows dynamically generated at
912
+ runtime.
885
913
  """
886
914
  if prepend:
887
915
  self.name = append_str + self.name
888
916
  else:
889
917
  self.name += append_str
890
918
 
919
+ if dynamic:
920
+ self.name_updates.append({"append_str": append_str, "prepend": prepend})
921
+
891
922
  def update_metadata(
892
923
  self,
893
924
  update: dict[str, Any],
@@ -895,6 +926,7 @@ class Job(MSONable):
895
926
  function_filter: Callable = None,
896
927
  dict_mod: bool = False,
897
928
  dynamic: bool = True,
929
+ callback_filter: Callable[[jobflow.Flow | Job], bool] = lambda _: True,
898
930
  ):
899
931
  """
900
932
  Update the metadata of the job.
@@ -918,6 +950,9 @@ class Job(MSONable):
918
950
  dynamic
919
951
  The updates will be propagated to Jobs/Flows dynamically generated at
920
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.
921
956
 
922
957
  Examples
923
958
  --------
@@ -936,11 +971,16 @@ class Job(MSONable):
936
971
  will not only set the `example` metadata to the `test_job`, but also to all the
937
972
  new Jobs that will be generated at runtime by the ExampleMaker.
938
973
 
939
- `update_metadata` can be called multiple times with different `name_filter` or
940
- `function_filter` to control which Jobs will be updated.
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:
941
976
 
942
- At variance, if `dynamic` is set to `False` the `example` metadata will only be
943
- added to the `test_job` and not to the generated Jobs.
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.
944
984
  """
945
985
  from jobflow.utils.dict_mods import apply_mod
946
986
 
@@ -950,6 +990,7 @@ class Job(MSONable):
950
990
  "name_filter": name_filter,
951
991
  "function_filter": function_filter,
952
992
  "dict_mod": dict_mod,
993
+ "callback_filter": callback_filter,
953
994
  }
954
995
  self.metadata_updates.append(dict_input)
955
996
 
@@ -957,7 +998,6 @@ class Job(MSONable):
957
998
  function_filter = getattr(function_filter, "__wrapped__", function_filter)
958
999
  function = getattr(self.function, "__wrapped__", self.function)
959
1000
 
960
- # if function_filter is not None and function_filter != self.function:
961
1001
  if function_filter is not None and function_filter != function:
962
1002
  return
963
1003
 
@@ -966,6 +1006,9 @@ class Job(MSONable):
966
1006
  ):
967
1007
  return
968
1008
 
1009
+ if callback_filter(self) is False:
1010
+ return
1011
+
969
1012
  # if we get to here then we pass all the filters
970
1013
  if dict_mod:
971
1014
  apply_mod(update, self.metadata)
@@ -1134,8 +1177,8 @@ class Job(MSONable):
1134
1177
  prepend
1135
1178
  Insert the UUIDs at the beginning of the list rather than extending it.
1136
1179
  """
1137
- if not isinstance(hosts_uuids, (list, tuple)):
1138
- hosts_uuids = [hosts_uuids] # type: ignore
1180
+ if isinstance(hosts_uuids, str):
1181
+ hosts_uuids = [hosts_uuids]
1139
1182
  if prepend:
1140
1183
  self.hosts[0:0] = hosts_uuids
1141
1184
  else:
@@ -1170,6 +1213,8 @@ class Response(typing.Generic[T]):
1170
1213
  Stop any children of the current flow.
1171
1214
  stop_jobflow
1172
1215
  Stop executing all remaining jobs.
1216
+ job_dir
1217
+ The directory where the job was run.
1173
1218
  """
1174
1219
 
1175
1220
  output: T = None
@@ -1179,13 +1224,15 @@ class Response(typing.Generic[T]):
1179
1224
  stored_data: dict[Hashable, Any] = None
1180
1225
  stop_children: bool = False
1181
1226
  stop_jobflow: bool = False
1227
+ job_dir: str | Path = None
1182
1228
 
1183
1229
  @classmethod
1184
1230
  def from_job_returns(
1185
1231
  cls,
1186
1232
  job_returns: Any | None,
1187
1233
  output_schema: type[BaseModel] = None,
1188
- ) -> Response:
1234
+ job_dir: str | Path = None,
1235
+ ) -> Self:
1189
1236
  """
1190
1237
  Generate a :obj:`Response` from the outputs of a :obj:`Job`.
1191
1238
 
@@ -1199,6 +1246,8 @@ class Response(typing.Generic[T]):
1199
1246
  output_schema
1200
1247
  A pydantic model associated with the job. Used to enforce a schema for the
1201
1248
  outputs.
1249
+ job_dir
1250
+ The directory where the job was run.
1202
1251
 
1203
1252
  Raises
1204
1253
  ------
@@ -1215,17 +1264,18 @@ class Response(typing.Generic[T]):
1215
1264
  # only apply output schema if there is no replace.
1216
1265
  job_returns.output = apply_schema(job_returns.output, output_schema)
1217
1266
 
1218
- return job_returns
1267
+ job_returns.job_dir = job_dir
1268
+ return cast(Self, job_returns)
1219
1269
 
1220
1270
  if isinstance(job_returns, (list, tuple)):
1221
1271
  # check that a Response object is not given as one of many outputs
1222
- for r in job_returns:
1223
- if isinstance(r, Response):
1272
+ for resp in job_returns:
1273
+ if isinstance(resp, Response):
1224
1274
  raise ValueError(
1225
1275
  "Response cannot be returned in combination with other outputs."
1226
1276
  )
1227
1277
 
1228
- return cls(output=apply_schema(job_returns, output_schema))
1278
+ return cls(output=apply_schema(job_returns, output_schema), job_dir=job_dir)
1229
1279
 
1230
1280
 
1231
1281
  def apply_schema(output: Any, schema: type[BaseModel] | None):
@@ -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
@@ -269,9 +269,7 @@ def recursive_call(
269
269
  return False
270
270
  if name_filter is not None and name_filter not in nested_obj.name:
271
271
  return False
272
- if class_filter is not None and not isinstance(nested_obj, class_filter):
273
- return False
274
- return True
272
+ return class_filter is None or isinstance(nested_obj, class_filter)
275
273
 
276
274
  d = obj.as_dict()
277
275
 
@@ -82,7 +82,7 @@ class OutputReference(MSONable):
82
82
  OutputReference(1234, ['key'], [0], .value)
83
83
  """
84
84
 
85
- __slots__ = ("uuid", "attributes", "output_schema")
85
+ __slots__ = ("attributes", "output_schema", "uuid")
86
86
 
87
87
  def __init__(
88
88
  self,
@@ -17,6 +17,7 @@ if typing.TYPE_CHECKING:
17
17
  from typing import Any, Optional, Union
18
18
 
19
19
  from maggma.core import Sort
20
+ from typing_extensions import Self
20
21
 
21
22
  from jobflow.core.schemas import JobStoreDocument
22
23
 
@@ -545,9 +546,9 @@ class JobStore(Store):
545
546
  )
546
547
 
547
548
  @classmethod
548
- def from_file(cls: type[T], db_file: str | Path, **kwargs) -> T:
549
+ def from_file(cls, db_file: str | Path, **kwargs) -> Self:
549
550
  """
550
- Create an JobStore from a database file.
551
+ Create a JobStore from a database file.
551
552
 
552
553
  Two options are supported for the database file. The file should be in json or
553
554
  yaml format.
@@ -555,7 +556,7 @@ class JobStore(Store):
555
556
  The simplest format is a monty dumped version of the store, generated using:
556
557
 
557
558
  >>> from monty.serialization import dumpfn
558
- >>> dumpfn("job_store.yaml", job_store)
559
+ >>> dumpfn(job_store, "job_store.yaml")
559
560
 
560
561
  Alternatively, the file can contain the keys docs_store, additional_stores and
561
562
  any other keyword arguments supported by the :obj:`JobStore` constructor. The
@@ -605,7 +606,7 @@ class JobStore(Store):
605
606
  return cls.from_dict_spec(store_info, **kwargs)
606
607
 
607
608
  @classmethod
608
- def from_dict_spec(cls: type[T], spec: dict, **kwargs) -> T:
609
+ def from_dict_spec(cls, spec: dict, **kwargs) -> Self:
609
610
  """
610
611
  Create an JobStore from a dict specification.
611
612
 
@@ -5,6 +5,8 @@ from __future__ import annotations
5
5
  import typing
6
6
 
7
7
  from fireworks import FiretaskBase, Firework, FWAction, Workflow, explicit_serialize
8
+ from fireworks.utilities.fw_serializers import recursive_serialize, serialize_fw
9
+ from monty.json import jsanitize
8
10
 
9
11
  if typing.TYPE_CHECKING:
10
12
  from collections.abc import Sequence
@@ -197,3 +199,16 @@ class JobFiretask(FiretaskBase):
197
199
  defuse_workflow=response.stop_jobflow,
198
200
  defuse_children=response.stop_children,
199
201
  )
202
+
203
+ @serialize_fw
204
+ @recursive_serialize
205
+ def to_dict(self) -> dict:
206
+ """
207
+ Serialize version of the FireTask.
208
+
209
+ Overrides the original method to explicitly jsanitize the Job
210
+ to handle cases not properly handled by fireworks, like a Callable.
211
+ """
212
+ d = dict(self)
213
+ d["job"] = jsanitize(d["job"].as_dict())
214
+ return d
@@ -15,8 +15,8 @@ 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,
19
- store: jobflow.JobStore = None,
18
+ log: bool | str = True,
19
+ store: jobflow.JobStore | None = None,
20
20
  create_folders: bool = False,
21
21
  root_dir: str | Path | None = None,
22
22
  ensure_success: bool = False,
@@ -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
- Whether to print log messages.
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
@@ -58,7 +61,7 @@ def run_locally(
58
61
  The responses of the jobs, as a dict of ``{uuid: {index: response}}``.
59
62
  """
60
63
  from collections import defaultdict
61
- from datetime import datetime
64
+ from datetime import datetime, timezone
62
65
  from pathlib import Path
63
66
  from random import randint
64
67
 
@@ -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
 
@@ -152,7 +155,7 @@ def run_locally(
152
155
 
153
156
  def _get_job_dir():
154
157
  if create_folders:
155
- time_now = datetime.utcnow().strftime(SETTINGS.DIRECTORY_FORMAT)
158
+ time_now = datetime.now(tz=timezone.utc).strftime(SETTINGS.DIRECTORY_FORMAT)
156
159
  job_dir = root_dir / f"job_{time_now}-{randint(10000, 99999)}"
157
160
  job_dir.mkdir()
158
161
  return job_dir
@@ -165,6 +168,8 @@ def run_locally(
165
168
  with cd(job_dir):
166
169
  response, jobflow_stopped = _run_job(job, parents)
167
170
 
171
+ if response is not None:
172
+ response.job_dir = job_dir
168
173
  encountered_bad_response = encountered_bad_response or response is None
169
174
  if jobflow_stopped:
170
175
  return False
@@ -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 modify ~/.jobflow.yaml. Alternatively,
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
- "%Y-%m-%d-%H-%M-%S-%f",
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. "
@@ -1,4 +1,5 @@
1
1
  """Utility functions for logging, enumerations and manipulating dictionaries."""
2
+
2
3
  from jobflow.utils.enum import ValueEnum
3
4
  from jobflow.utils.find import (
4
5
  contains_flow_or_job,
@@ -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
@@ -1,4 +1,5 @@
1
1
  """Tools for generating UUIDs."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from uuid import UUID
@@ -11,7 +12,7 @@ except ImportError: # pragma: no cover
11
12
  "Install it with `pip install jobflow[ulid]` or `pip install python-ulid`."
12
13
  )
13
14
 
14
- class ULID: # type: ignore
15
+ class ULID: # type: ignore[no-redef]
15
16
  """Fake ULID class for raising import error."""
16
17
 
17
18
  def __init__(self, *args, **kwargs):
@@ -1,4 +1,5 @@
1
1
  """Tools for generating UUIDs."""
2
+
2
3
  from monty.dev import deprecated
3
4
 
4
5
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jobflow
3
- Version: 0.1.17
3
+ Version: 0.1.19
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
@@ -34,19 +34,20 @@ Requires-Dist: pydash
34
34
  Provides-Extra: ulid
35
35
  Requires-Dist: python-ulid; extra == "ulid"
36
36
  Provides-Extra: docs
37
- Requires-Dist: autodoc_pydantic==2.0.1; extra == "docs"
38
- Requires-Dist: furo==2023.9.10; extra == "docs"
39
- Requires-Dist: ipython==8.20.0; extra == "docs"
40
- Requires-Dist: myst_parser==2.0.0; extra == "docs"
41
- Requires-Dist: nbsphinx==0.9.3; extra == "docs"
37
+ Requires-Dist: autodoc_pydantic==2.1.0; extra == "docs"
38
+ Requires-Dist: furo==2024.8.6; extra == "docs"
39
+ Requires-Dist: ipython==8.29.0; extra == "docs"
40
+ Requires-Dist: myst_parser==4.0.0; extra == "docs"
41
+ Requires-Dist: nbsphinx==0.9.5; extra == "docs"
42
42
  Requires-Dist: sphinx-copybutton==0.5.2; extra == "docs"
43
- Requires-Dist: sphinx==7.2.6; extra == "docs"
43
+ Requires-Dist: sphinx==8.1.3; extra == "docs"
44
44
  Provides-Extra: dev
45
45
  Requires-Dist: pre-commit>=2.12.1; extra == "dev"
46
+ Requires-Dist: typing_extensions; python_version < "3.11" and extra == "dev"
46
47
  Provides-Extra: tests
47
48
  Requires-Dist: moto==4.2.13; extra == "tests"
48
- Requires-Dist: pytest-cov==4.1.0; extra == "tests"
49
- Requires-Dist: pytest==7.4.4; extra == "tests"
49
+ Requires-Dist: pytest-cov==6.0.0; extra == "tests"
50
+ Requires-Dist: pytest==8.3.3; extra == "tests"
50
51
  Provides-Extra: vis
51
52
  Requires-Dist: matplotlib; extra == "vis"
52
53
  Requires-Dist: pydot; extra == "vis"
@@ -54,20 +55,22 @@ Provides-Extra: fireworks
54
55
  Requires-Dist: FireWorks; extra == "fireworks"
55
56
  Provides-Extra: strict
56
57
  Requires-Dist: FireWorks==2.0.3; extra == "strict"
57
- Requires-Dist: PyYAML==6.0.1; extra == "strict"
58
- Requires-Dist: maggma==0.61.0; extra == "strict"
59
- Requires-Dist: matplotlib==3.8.2; extra == "strict"
60
- Requires-Dist: monty==2023.11.3; extra == "strict"
58
+ Requires-Dist: PyYAML==6.0.2; extra == "strict"
59
+ Requires-Dist: maggma==0.70.0; extra == "strict"
60
+ Requires-Dist: matplotlib==3.9.2; extra == "strict"
61
+ Requires-Dist: monty==2024.10.21; extra == "strict"
61
62
  Requires-Dist: moto==4.2.13; extra == "strict"
62
63
  Requires-Dist: networkx==3.2.1; extra == "strict"
63
- Requires-Dist: pydantic-settings==2.1.0; extra == "strict"
64
- Requires-Dist: pydantic==2.5.3; extra == "strict"
65
- Requires-Dist: pydash==7.0.6; extra == "strict"
64
+ Requires-Dist: pydantic-settings==2.6.1; extra == "strict"
65
+ Requires-Dist: pydantic==2.9.2; extra == "strict"
66
+ Requires-Dist: pydash==8.0.4; extra == "strict"
66
67
  Requires-Dist: pydot==2.0.0; extra == "strict"
67
- Requires-Dist: typing-extensions==4.9.0; extra == "strict"
68
- Requires-Dist: python-ulid==2.2.0; extra == "strict"
68
+ Requires-Dist: python-ulid==3.0.0; extra == "strict"
69
+ Requires-Dist: typing-extensions==4.12.2; extra == "strict"
69
70
 
70
- # jobflow
71
+ <div align="center">
72
+
73
+ # ![Jobflow](docs/_static/img/jobflow_logo.svg)
71
74
 
72
75
  [![tests](https://img.shields.io/github/actions/workflow/status/materialsproject/jobflow/testing.yml?branch=main&label=tests)](https://github.com/materialsproject/jobflow/actions?query=workflow%3Atesting)
73
76
  [![code coverage](https://img.shields.io/codecov/c/gh/materialsproject/jobflow/main)](https://codecov.io/gh/materialsproject/jobflow/)
@@ -75,11 +78,14 @@ Requires-Dist: python-ulid==2.2.0; extra == "strict"
75
78
  ![supported python versions](https://img.shields.io/pypi/pyversions/jobflow)
76
79
  [![DOI](https://joss.theoj.org/papers/10.21105/joss.05995/status.svg)](https://doi.org/10.21105/joss.05995)
77
80
 
81
+ </div>
82
+
78
83
  [Documentation](https://materialsproject.github.io/jobflow/) | [PyPI](https://pypi.org/project/jobflow/) | [GitHub](https://github.com/materialsproject/jobflow) | [Paper](https://doi.org/10.21105/joss.05995)
79
84
 
80
85
  Jobflow is a free, open-source library for writing and executing workflows. Complex
81
86
  workflows can be defined using simple python functions and executed locally or on
82
- arbitrary computing resources using the [FireWorks][fireworks] workflow manager.
87
+ arbitrary computing resources using the [jobflow-remote][jfr] or [FireWorks][fireworks]
88
+ workflow managers.
83
89
 
84
90
  Some features that distinguish jobflow are dynamic workflows, easy compositing and
85
91
  connecting of workflows, and the ability to store workflow outputs across multiple
@@ -98,7 +104,7 @@ Some of its features include:
98
104
  way to build complex workflows.
99
105
  - Integration with multiple databases (MongoDB, S3, GridFS, and more) through the
100
106
  [Maggma][maggma] package.
101
- - Support for the [FireWorks][fireworks] workflow management system, allowing workflow
107
+ - Support for the [jobflow-remote][jfr] and [FireWorks][fireworks] workflow management systems, allowing workflow
102
108
  execution on multicore machines or through a queue, on a single machine or multiple
103
109
  machines.
104
110
  - Support for dynamic workflows — workflows that modify themselves or create new ones
@@ -192,6 +198,7 @@ Jobflow was designed by Alex Ganose, Anubhav Jain, Gian-Marco Rignanese, David W
192
198
 
193
199
  [maggma]: https://materialsproject.github.io/maggma/
194
200
  [fireworks]: https://materialsproject.github.io/fireworks/
201
+ [jfr]: https://matgenix.github.io/jobflow-remote
195
202
  [help-forum]: https://matsci.org/c/fireworks
196
203
  [issues]: https://github.com/materialsproject/jobflow/issues
197
204
  [changelog]: https://materialsproject.github.io/jobflow/changelog.html
@@ -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.1.0
17
+ furo==2024.8.6
18
+ ipython==8.29.0
19
+ myst_parser==4.0.0
20
+ nbsphinx==0.9.5
21
+ sphinx-copybutton==0.5.2
22
+ sphinx==8.1.3
23
+
24
+ [fireworks]
25
+ FireWorks
26
+
27
+ [strict]
28
+ FireWorks==2.0.3
29
+ PyYAML==6.0.2
30
+ maggma==0.70.0
31
+ matplotlib==3.9.2
32
+ monty==2024.10.21
33
+ moto==4.2.13
34
+ networkx==3.2.1
35
+ pydantic-settings==2.6.1
36
+ pydantic==2.9.2
37
+ pydash==8.0.4
38
+ pydot==2.0.0
39
+ python-ulid==3.0.0
40
+ typing-extensions==4.12.2
41
+
42
+ [tests]
43
+ moto==4.2.13
44
+ pytest-cov==6.0.0
45
+ pytest==8.3.3
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,49 +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
- [docs]
13
- autodoc_pydantic==2.0.1
14
- furo==2023.9.10
15
- ipython==8.20.0
16
- myst_parser==2.0.0
17
- nbsphinx==0.9.3
18
- sphinx-copybutton==0.5.2
19
- sphinx==7.2.6
20
-
21
- [fireworks]
22
- FireWorks
23
-
24
- [strict]
25
- FireWorks==2.0.3
26
- PyYAML==6.0.1
27
- maggma==0.61.0
28
- matplotlib==3.8.2
29
- monty==2023.11.3
30
- moto==4.2.13
31
- networkx==3.2.1
32
- pydantic-settings==2.1.0
33
- pydantic==2.5.3
34
- pydash==7.0.6
35
- pydot==2.0.0
36
- typing-extensions==4.9.0
37
- python-ulid==2.2.0
38
-
39
- [tests]
40
- moto==4.2.13
41
- pytest-cov==4.1.0
42
- pytest==7.4.4
43
-
44
- [ulid]
45
- python-ulid
46
-
47
- [vis]
48
- matplotlib
49
- pydot
File without changes
File without changes
File without changes
File without changes