jobflow 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
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/__init__.py +1 -1
- jobflow/core/flow.py +115 -1
- jobflow/core/job.py +82 -22
- jobflow/core/reference.py +39 -4
- jobflow/core/store.py +19 -5
- {jobflow-0.2.0.dist-info → jobflow-0.3.0.dist-info}/METADATA +23 -23
- {jobflow-0.2.0.dist-info → jobflow-0.3.0.dist-info}/RECORD +10 -10
- {jobflow-0.2.0.dist-info → jobflow-0.3.0.dist-info}/WHEEL +1 -1
- {jobflow-0.2.0.dist-info → jobflow-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {jobflow-0.2.0.dist-info → jobflow-0.3.0.dist-info}/top_level.txt +0 -0
jobflow/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Jobflow is a package for writing dynamic and connected workflows."""
|
|
2
2
|
|
|
3
3
|
from jobflow._version import __version__
|
|
4
|
-
from jobflow.core.flow import Flow, JobOrder
|
|
4
|
+
from jobflow.core.flow import Flow, JobOrder, flow
|
|
5
5
|
from jobflow.core.job import Job, JobConfig, Response, job
|
|
6
6
|
from jobflow.core.maker import Maker
|
|
7
7
|
from jobflow.core.reference import OnMissing, OutputReference
|
jobflow/core/flow.py
CHANGED
|
@@ -4,6 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import warnings
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from contextvars import ContextVar
|
|
7
9
|
from copy import deepcopy
|
|
8
10
|
from typing import TYPE_CHECKING
|
|
9
11
|
|
|
@@ -155,6 +157,12 @@ class Flow(MSONable):
|
|
|
155
157
|
self.add_jobs(jobs)
|
|
156
158
|
self.output = output
|
|
157
159
|
|
|
160
|
+
# If we're running inside a `DecoratedFlow`, add *this* Flow to the
|
|
161
|
+
# context.
|
|
162
|
+
current_flow_children_list = _current_flow_context.get()
|
|
163
|
+
if current_flow_children_list is not None:
|
|
164
|
+
current_flow_children_list.append(self)
|
|
165
|
+
|
|
158
166
|
def __len__(self) -> int:
|
|
159
167
|
"""Get the number of jobs or subflows in the flow."""
|
|
160
168
|
return len(self.jobs)
|
|
@@ -706,6 +714,7 @@ class Flow(MSONable):
|
|
|
706
714
|
function_filter: Callable = None,
|
|
707
715
|
attributes: list[str] | str = None,
|
|
708
716
|
dynamic: bool = True,
|
|
717
|
+
dict_mod: bool = False,
|
|
709
718
|
):
|
|
710
719
|
"""
|
|
711
720
|
Update the job config of all Jobs in the Flow.
|
|
@@ -728,6 +737,9 @@ class Flow(MSONable):
|
|
|
728
737
|
dynamic
|
|
729
738
|
The updates will be propagated to Jobs/Flows dynamically generated at
|
|
730
739
|
runtime.
|
|
740
|
+
dict_mod
|
|
741
|
+
Use the dict mod language to apply updates. See :obj:`.DictMods` for more
|
|
742
|
+
details.
|
|
731
743
|
|
|
732
744
|
Examples
|
|
733
745
|
--------
|
|
@@ -767,6 +779,7 @@ class Flow(MSONable):
|
|
|
767
779
|
function_filter=function_filter,
|
|
768
780
|
attributes=attributes,
|
|
769
781
|
dynamic=dynamic,
|
|
782
|
+
dict_mod=dict_mod,
|
|
770
783
|
)
|
|
771
784
|
|
|
772
785
|
def add_hosts_uuids(
|
|
@@ -823,7 +836,7 @@ class Flow(MSONable):
|
|
|
823
836
|
if job.host is not None and job.host != self.uuid:
|
|
824
837
|
raise ValueError(
|
|
825
838
|
f"{type(job).__name__} {job.name} ({job.uuid}) already belongs "
|
|
826
|
-
f"to another flow."
|
|
839
|
+
f"to another flow: {job.host}."
|
|
827
840
|
)
|
|
828
841
|
if job.uuid in job_ids:
|
|
829
842
|
raise ValueError(
|
|
@@ -916,3 +929,104 @@ def get_flow(
|
|
|
916
929
|
)
|
|
917
930
|
|
|
918
931
|
return flow
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
class DecoratedFlow(Flow):
|
|
935
|
+
"""A DecoratedFlow is a Flow that is returned on using the @flow decorator."""
|
|
936
|
+
|
|
937
|
+
def __init__(self, fn, *args, **kwargs):
|
|
938
|
+
from jobflow import Maker
|
|
939
|
+
|
|
940
|
+
self.fn = fn
|
|
941
|
+
self.args = args
|
|
942
|
+
self.kwargs = kwargs
|
|
943
|
+
|
|
944
|
+
# Collect the jobs and flows that are used in the function
|
|
945
|
+
children_list = []
|
|
946
|
+
with flow_build_context(children_list):
|
|
947
|
+
output = self.fn(*self.args, **self.kwargs)
|
|
948
|
+
|
|
949
|
+
# From the collected items, remove those that have already been assigned
|
|
950
|
+
# to another Flow during the call of the function.
|
|
951
|
+
# This handles the case where a Flow object is instantiated inside
|
|
952
|
+
# the decorated function
|
|
953
|
+
children_list = [c for c in children_list if c.host is None]
|
|
954
|
+
|
|
955
|
+
name = getattr(self.fn, "__qualname__", self.fn.__name__)
|
|
956
|
+
|
|
957
|
+
# if decorates a make() in a Maker use that as a name
|
|
958
|
+
if (
|
|
959
|
+
len(self.args) > 0
|
|
960
|
+
and name.split(".")[-1] == "make"
|
|
961
|
+
and getattr(args[0], self.fn.__name__, None)
|
|
962
|
+
and isinstance(args[0], Maker)
|
|
963
|
+
):
|
|
964
|
+
name = args[0].name
|
|
965
|
+
|
|
966
|
+
if isinstance(output, (jobflow.Job, jobflow.Flow)):
|
|
967
|
+
warnings.warn(
|
|
968
|
+
f"@flow decorated function '{name}' contains a Flow or"
|
|
969
|
+
f"Job as an output. Usually the output should be the output of"
|
|
970
|
+
f"a Job or another Flow (e.g. job.output). Replacing the"
|
|
971
|
+
f"output of the @flow with the output of the Flow/Job."
|
|
972
|
+
f"If this message is unexpected then double check the outputs"
|
|
973
|
+
f"of your @flow decorated function.",
|
|
974
|
+
stacklevel=2,
|
|
975
|
+
)
|
|
976
|
+
output = output.output
|
|
977
|
+
|
|
978
|
+
super().__init__(name=name, jobs=children_list, output=output)
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def flow(fn):
|
|
982
|
+
"""
|
|
983
|
+
Turn a function into a DecoratedFlow object.
|
|
984
|
+
|
|
985
|
+
Parameters
|
|
986
|
+
----------
|
|
987
|
+
fn (Callable): The function to be wrapped in a DecoratedFlow object.
|
|
988
|
+
|
|
989
|
+
Returns
|
|
990
|
+
-------
|
|
991
|
+
Callable: A wrapper function that, when called, creates and returns
|
|
992
|
+
an instance of DecoratedFlow initialized with the provided function
|
|
993
|
+
and its arguments.
|
|
994
|
+
"""
|
|
995
|
+
from functools import wraps
|
|
996
|
+
|
|
997
|
+
@wraps(fn)
|
|
998
|
+
def wrapper(*args, **kwargs):
|
|
999
|
+
return DecoratedFlow(fn, *args, **kwargs)
|
|
1000
|
+
|
|
1001
|
+
return wrapper
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
@contextmanager
|
|
1005
|
+
def flow_build_context(children_list):
|
|
1006
|
+
"""Provide a context manager for flows.
|
|
1007
|
+
|
|
1008
|
+
Provides a context manager for setting and resetting the `Job` and `Flow`
|
|
1009
|
+
objects in the current flow context.
|
|
1010
|
+
|
|
1011
|
+
Parameters
|
|
1012
|
+
----------
|
|
1013
|
+
children_list: The `Job` or `Flow` objects that are part of the current
|
|
1014
|
+
flow context.
|
|
1015
|
+
|
|
1016
|
+
Yields
|
|
1017
|
+
------
|
|
1018
|
+
None: Temporarily sets the provided `Job` or `Flow` objects as
|
|
1019
|
+
belonging to the current flow context within the managed block.
|
|
1020
|
+
|
|
1021
|
+
Raises
|
|
1022
|
+
------
|
|
1023
|
+
None
|
|
1024
|
+
"""
|
|
1025
|
+
token = _current_flow_context.set(children_list)
|
|
1026
|
+
try:
|
|
1027
|
+
yield
|
|
1028
|
+
finally:
|
|
1029
|
+
_current_flow_context.reset(token)
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
_current_flow_context = ContextVar("current_flow_context", default=None)
|
jobflow/core/job.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import cast, overload
|
|
|
11
11
|
from monty.json import MSONable, jsanitize
|
|
12
12
|
from typing_extensions import Self
|
|
13
13
|
|
|
14
|
+
from jobflow.core.flow import _current_flow_context
|
|
14
15
|
from jobflow.core.reference import OnMissing, OutputReference
|
|
15
16
|
from jobflow.utils.uid import suid
|
|
16
17
|
|
|
@@ -384,6 +385,30 @@ class Job(MSONable):
|
|
|
384
385
|
stacklevel=2,
|
|
385
386
|
)
|
|
386
387
|
|
|
388
|
+
# If we're running inside a `DecoratedFlow`, add *this* Job to the
|
|
389
|
+
# context.
|
|
390
|
+
current_flow_children_list = _current_flow_context.get()
|
|
391
|
+
if current_flow_children_list is not None:
|
|
392
|
+
current_flow_children_list.append(self)
|
|
393
|
+
|
|
394
|
+
def __getitem__(self, key: Any) -> OutputReference:
|
|
395
|
+
"""
|
|
396
|
+
Get the corresponding `OutputReference` for the `Job`.
|
|
397
|
+
|
|
398
|
+
This is for when it is indexed like a dictionary or list.
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
key
|
|
403
|
+
The index/key.
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
OutputReference
|
|
408
|
+
The equivalent of `Job.output[k]`
|
|
409
|
+
"""
|
|
410
|
+
return self.output[key]
|
|
411
|
+
|
|
387
412
|
def __repr__(self):
|
|
388
413
|
"""Get a string representation of the job."""
|
|
389
414
|
name, uuid = self.name, self.uuid
|
|
@@ -1022,6 +1047,7 @@ class Job(MSONable):
|
|
|
1022
1047
|
function_filter: Callable = None,
|
|
1023
1048
|
attributes: list[str] | str = None,
|
|
1024
1049
|
dynamic: bool = True,
|
|
1050
|
+
dict_mod: bool = False,
|
|
1025
1051
|
):
|
|
1026
1052
|
"""
|
|
1027
1053
|
Update the job config.
|
|
@@ -1045,6 +1071,9 @@ class Job(MSONable):
|
|
|
1045
1071
|
dynamic
|
|
1046
1072
|
The updates will be propagated to Jobs/Flows dynamically generated at
|
|
1047
1073
|
runtime.
|
|
1074
|
+
dict_mod
|
|
1075
|
+
Use the dict mod language to apply updates. See :obj:`.DictMods` for more
|
|
1076
|
+
details.
|
|
1048
1077
|
|
|
1049
1078
|
Examples
|
|
1050
1079
|
--------
|
|
@@ -1096,12 +1125,23 @@ class Job(MSONable):
|
|
|
1096
1125
|
At variance, if `dynamic` is set to `False` the `manager_config` option will
|
|
1097
1126
|
only be set for the `test_job` and not for the generated Jobs.
|
|
1098
1127
|
"""
|
|
1128
|
+
from jobflow.utils.dict_mods import apply_mod
|
|
1129
|
+
|
|
1130
|
+
if dict_mod and attributes:
|
|
1131
|
+
raise ValueError("dict_mod and attributes options cannot be used together")
|
|
1132
|
+
|
|
1133
|
+
if dict_mod and isinstance(config, JobConfig):
|
|
1134
|
+
raise ValueError(
|
|
1135
|
+
"If dict_mod is selected the update config cannot be a JobConfig object"
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1099
1138
|
if dynamic:
|
|
1100
1139
|
dict_input = {
|
|
1101
1140
|
"config": config,
|
|
1102
1141
|
"name_filter": name_filter,
|
|
1103
1142
|
"function_filter": function_filter,
|
|
1104
1143
|
"attributes": attributes,
|
|
1144
|
+
"dict_mod": dict_mod,
|
|
1105
1145
|
}
|
|
1106
1146
|
self.config_updates.append(dict_input)
|
|
1107
1147
|
|
|
@@ -1119,29 +1159,34 @@ class Job(MSONable):
|
|
|
1119
1159
|
return
|
|
1120
1160
|
|
|
1121
1161
|
# if we get to here then we pass all the filters
|
|
1122
|
-
if
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
attributes = [attributes] if isinstance(attributes, str) else attributes
|
|
1128
|
-
if not set(attributes).issubset(set(config.keys())):
|
|
1129
|
-
raise ValueError(
|
|
1130
|
-
"Specified attributes include a key that is not present in the "
|
|
1131
|
-
"config dictionary."
|
|
1132
|
-
)
|
|
1133
|
-
config = JobConfig(**config)
|
|
1134
|
-
|
|
1135
|
-
if attributes is None:
|
|
1136
|
-
# overwrite the whole config
|
|
1137
|
-
self.config = config
|
|
1162
|
+
if dict_mod:
|
|
1163
|
+
conf_dict = self.config.as_dict()
|
|
1164
|
+
apply_mod(config, conf_dict)
|
|
1165
|
+
self.config = JobConfig.from_dict(conf_dict)
|
|
1138
1166
|
else:
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1167
|
+
if isinstance(config, dict):
|
|
1168
|
+
# convert dict specification to a JobConfig but set the attributes
|
|
1169
|
+
if attributes is None:
|
|
1170
|
+
attributes = list(config.keys())
|
|
1171
|
+
|
|
1172
|
+
attributes = [attributes] if isinstance(attributes, str) else attributes
|
|
1173
|
+
if not set(attributes).issubset(set(config.keys())):
|
|
1174
|
+
raise ValueError(
|
|
1175
|
+
"Specified attributes include a key that is not present in the "
|
|
1176
|
+
"config dictionary."
|
|
1177
|
+
)
|
|
1178
|
+
config = JobConfig(**config)
|
|
1179
|
+
|
|
1180
|
+
if attributes is None:
|
|
1181
|
+
# overwrite the whole config
|
|
1182
|
+
self.config = config
|
|
1183
|
+
else:
|
|
1184
|
+
# only update the specified attributes
|
|
1185
|
+
attributes = [attributes] if isinstance(attributes, str) else attributes
|
|
1186
|
+
for attr in attributes:
|
|
1187
|
+
if not hasattr(self.config, attr):
|
|
1188
|
+
raise ValueError(f"Unknown JobConfig attribute: {attr}")
|
|
1189
|
+
setattr(self.config, attr, getattr(config, attr))
|
|
1145
1190
|
|
|
1146
1191
|
def as_dict(self) -> dict:
|
|
1147
1192
|
"""Serialize the job as a dictionary."""
|
|
@@ -1259,6 +1304,21 @@ class Response(typing.Generic[T]):
|
|
|
1259
1304
|
Response
|
|
1260
1305
|
The job response controlling the data to store and flow execution options.
|
|
1261
1306
|
"""
|
|
1307
|
+
# If the Job returns another Job, or something that can be interpreted
|
|
1308
|
+
# as an iterable of jobs, interpret it as a replace.
|
|
1309
|
+
from jobflow import Flow
|
|
1310
|
+
|
|
1311
|
+
def is_job_or_flow(x):
|
|
1312
|
+
return isinstance(x, Job | Flow)
|
|
1313
|
+
|
|
1314
|
+
should_replace = is_job_or_flow(job_returns)
|
|
1315
|
+
if isinstance(job_returns, (list, tuple)):
|
|
1316
|
+
should_replace = all(is_job_or_flow(resp) for resp in job_returns)
|
|
1317
|
+
|
|
1318
|
+
if should_replace:
|
|
1319
|
+
job_returns = Response(replace=job_returns)
|
|
1320
|
+
|
|
1321
|
+
# only apply output schema if there is no replace.
|
|
1262
1322
|
if isinstance(job_returns, Response):
|
|
1263
1323
|
if job_returns.replace is None:
|
|
1264
1324
|
# only apply output schema if there is no replace.
|
jobflow/core/reference.py
CHANGED
|
@@ -514,12 +514,47 @@ def validate_schema_access(
|
|
|
514
514
|
The BaseModel class associated with the item, if any.
|
|
515
515
|
"""
|
|
516
516
|
schema_dict = schema.model_json_schema()
|
|
517
|
-
|
|
517
|
+
item_in_schema = item in schema_dict["properties"]
|
|
518
|
+
property_like = isinstance(item, str) and has_property_like(schema, item)
|
|
519
|
+
if not item_in_schema and not property_like:
|
|
518
520
|
raise AttributeError(f"{schema.__name__} does not have attribute '{item}'.")
|
|
519
521
|
|
|
520
522
|
subschema = None
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
523
|
+
if item_in_schema:
|
|
524
|
+
item_type = schema.model_fields[item].annotation
|
|
525
|
+
if lenient_issubclass(item_type, BaseModel):
|
|
526
|
+
subschema = item_type
|
|
524
527
|
|
|
525
528
|
return True, subschema
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def has_property_like(obj_type: type, name: str) -> bool:
|
|
532
|
+
"""
|
|
533
|
+
Check if a class has an attribute and if it is property-like.
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
obj_type
|
|
538
|
+
The class that needs to be checked
|
|
539
|
+
name
|
|
540
|
+
The name of the attribute to be verified
|
|
541
|
+
|
|
542
|
+
Returns
|
|
543
|
+
-------
|
|
544
|
+
bool
|
|
545
|
+
True if the property corresponding to the name is property-like.
|
|
546
|
+
"""
|
|
547
|
+
if not hasattr(obj_type, name):
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
attr = getattr(obj_type, name)
|
|
551
|
+
|
|
552
|
+
if isinstance(attr, property):
|
|
553
|
+
return True
|
|
554
|
+
|
|
555
|
+
if callable(attr):
|
|
556
|
+
return False
|
|
557
|
+
|
|
558
|
+
# Check for custom property-like descriptors with __get__ but not callable
|
|
559
|
+
# If not, is not property-like.
|
|
560
|
+
return hasattr(attr, "__get__")
|
jobflow/core/store.py
CHANGED
|
@@ -21,9 +21,17 @@ if typing.TYPE_CHECKING:
|
|
|
21
21
|
|
|
22
22
|
from jobflow.core.schemas import JobStoreDocument
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
obj_save_type = Union[
|
|
25
|
+
str,
|
|
26
|
+
Enum,
|
|
27
|
+
type[MSONable],
|
|
28
|
+
list[Union[Enum, str, type[MSONable], list[Union[str, type[MSONable]]]]],
|
|
29
|
+
]
|
|
30
|
+
save_type = Optional[dict[str, obj_save_type]]
|
|
31
|
+
obj_load_type = Union[
|
|
32
|
+
str, Enum, type[MSONable], list[Union[Enum, str, type[MSONable]]]
|
|
33
|
+
]
|
|
34
|
+
load_type = Union[bool, dict[str, Union[bool, obj_load_type]]]
|
|
27
35
|
|
|
28
36
|
|
|
29
37
|
T = typing.TypeVar("T", bound="JobStore")
|
|
@@ -43,6 +51,9 @@ class JobStore(Store):
|
|
|
43
51
|
Which items to save in additional stores when uploading documents. Given as a
|
|
44
52
|
mapping of ``{store name: store_type}`` where ``store_type`` can be a dictionary
|
|
45
53
|
key (string or enum), an :obj:`.MSONable` class, or a list of keys/classes.
|
|
54
|
+
If the list of keys/classes itself contains a list/tuple, this will be treated
|
|
55
|
+
as the path to the save key. Can be used if a key is duplicate in the output
|
|
56
|
+
and only a single occurrence shall be put in the additional store.
|
|
46
57
|
load
|
|
47
58
|
Which items to load from additional stores when querying documents. Given as a
|
|
48
59
|
mapping of ``{store name: store_type}`` where ``store_type`` can be `True``, in
|
|
@@ -304,7 +315,10 @@ class JobStore(Store):
|
|
|
304
315
|
locations = []
|
|
305
316
|
for store_name, store_save in save_keys.items():
|
|
306
317
|
for save_key in store_save:
|
|
307
|
-
|
|
318
|
+
if isinstance(save_key, (list, tuple)):
|
|
319
|
+
locations.append(list(save_key))
|
|
320
|
+
else:
|
|
321
|
+
locations.extend(find_key(doc, save_key, include_end=True))
|
|
308
322
|
|
|
309
323
|
locations = get_root_locations(locations)
|
|
310
324
|
objects = [get(doc, list(loc)) for loc in locations]
|
|
@@ -726,7 +740,7 @@ def _prepare_load(
|
|
|
726
740
|
|
|
727
741
|
def _prepare_save(
|
|
728
742
|
save: bool | save_type,
|
|
729
|
-
) -> dict[str, list[str | type[MSONable]]]:
|
|
743
|
+
) -> dict[str, list[str | type[MSONable] | list[str | type[MSONable]]]]:
|
|
730
744
|
"""Standardize save type."""
|
|
731
745
|
from enum import Enum
|
|
732
746
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jobflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -25,49 +25,49 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
25
25
|
Requires-Python: >=3.10
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
|
-
Requires-Dist: PyYAML
|
|
29
|
-
Requires-Dist: maggma>=0.
|
|
28
|
+
Requires-Dist: PyYAML>=6.0.1
|
|
29
|
+
Requires-Dist: maggma>=0.72.0
|
|
30
30
|
Requires-Dist: monty>=2023.9.25
|
|
31
|
-
Requires-Dist: networkx
|
|
31
|
+
Requires-Dist: networkx>=3.2.1
|
|
32
32
|
Requires-Dist: pydantic-settings>=2.0.3
|
|
33
|
-
Requires-Dist: pydantic>=2.
|
|
34
|
-
Requires-Dist: pydash
|
|
33
|
+
Requires-Dist: pydantic>=2.4
|
|
34
|
+
Requires-Dist: pydash>=8.0.1
|
|
35
35
|
Provides-Extra: ulid
|
|
36
36
|
Requires-Dist: python-ulid; extra == "ulid"
|
|
37
37
|
Provides-Extra: docs
|
|
38
38
|
Requires-Dist: autodoc_pydantic==2.2.0; extra == "docs"
|
|
39
|
-
Requires-Dist: furo==
|
|
40
|
-
Requires-Dist: ipython==9.
|
|
39
|
+
Requires-Dist: furo==2025.12.19; extra == "docs"
|
|
40
|
+
Requires-Dist: ipython==9.9.0; extra == "docs"
|
|
41
41
|
Requires-Dist: myst_parser==4.0.1; extra == "docs"
|
|
42
|
-
Requires-Dist: nbsphinx==0.9.
|
|
42
|
+
Requires-Dist: nbsphinx==0.9.8; extra == "docs"
|
|
43
43
|
Requires-Dist: sphinx-copybutton==0.5.2; extra == "docs"
|
|
44
44
|
Requires-Dist: sphinx==8.1.3; extra == "docs"
|
|
45
45
|
Provides-Extra: dev
|
|
46
46
|
Requires-Dist: pre-commit>=2.12.1; extra == "dev"
|
|
47
47
|
Requires-Dist: typing_extensions; python_version < "3.11" and extra == "dev"
|
|
48
48
|
Provides-Extra: tests
|
|
49
|
-
Requires-Dist: moto==5.1.
|
|
50
|
-
Requires-Dist: pytest-cov==
|
|
51
|
-
Requires-Dist: pytest==
|
|
49
|
+
Requires-Dist: moto==5.1.20; extra == "tests"
|
|
50
|
+
Requires-Dist: pytest-cov==7.0.0; extra == "tests"
|
|
51
|
+
Requires-Dist: pytest==9.0.2; extra == "tests"
|
|
52
52
|
Provides-Extra: vis
|
|
53
53
|
Requires-Dist: matplotlib; extra == "vis"
|
|
54
54
|
Requires-Dist: pydot; extra == "vis"
|
|
55
55
|
Provides-Extra: fireworks
|
|
56
56
|
Requires-Dist: FireWorks; extra == "fireworks"
|
|
57
57
|
Provides-Extra: strict
|
|
58
|
-
Requires-Dist: FireWorks==2.0.
|
|
59
|
-
Requires-Dist: PyYAML==6.0.
|
|
60
|
-
Requires-Dist: maggma==0.
|
|
61
|
-
Requires-Dist: matplotlib==3.10.
|
|
58
|
+
Requires-Dist: FireWorks==2.0.8; extra == "strict"
|
|
59
|
+
Requires-Dist: PyYAML==6.0.3; extra == "strict"
|
|
60
|
+
Requires-Dist: maggma==0.72.0; extra == "strict"
|
|
61
|
+
Requires-Dist: matplotlib==3.10.8; extra == "strict"
|
|
62
62
|
Requires-Dist: monty==2025.3.3; extra == "strict"
|
|
63
|
-
Requires-Dist: moto==5.1.
|
|
63
|
+
Requires-Dist: moto==5.1.20; extra == "strict"
|
|
64
64
|
Requires-Dist: networkx==3.4.2; extra == "strict"
|
|
65
|
-
Requires-Dist: pydantic-settings==2.
|
|
66
|
-
Requires-Dist: pydantic==2.
|
|
67
|
-
Requires-Dist: pydash==8.0.
|
|
68
|
-
Requires-Dist: pydot==4.0.
|
|
69
|
-
Requires-Dist: python-ulid==3.
|
|
70
|
-
Requires-Dist: typing-extensions==4.
|
|
65
|
+
Requires-Dist: pydantic-settings==2.12.0; extra == "strict"
|
|
66
|
+
Requires-Dist: pydantic==2.12.5; extra == "strict"
|
|
67
|
+
Requires-Dist: pydash==8.0.6; extra == "strict"
|
|
68
|
+
Requires-Dist: pydot==4.0.1; extra == "strict"
|
|
69
|
+
Requires-Dist: python-ulid==3.1.0; extra == "strict"
|
|
70
|
+
Requires-Dist: typing-extensions==4.15.0; extra == "strict"
|
|
71
71
|
Dynamic: license-file
|
|
72
72
|
|
|
73
73
|
<div align="center">
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
jobflow/__init__.py,sha256=
|
|
1
|
+
jobflow/__init__.py,sha256=gKxdw3Yi4gjq6218fMBTHSBnVq-kZ4uZ9fLoKbC1xr0,576
|
|
2
2
|
jobflow/_version.py,sha256=Ym07PBD7sAmpqVpX8tuzWma3P_Hv6KXbDKXWkw8OwaI,205
|
|
3
3
|
jobflow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
jobflow/settings.py,sha256=yinHpix-DPwzcBhQCO8zDXFuv048rmBgpYMPa9wcs8c,6094
|
|
5
5
|
jobflow/core/__init__.py,sha256=3sx5t1Gysejc4c_fPrhvCjPUg0p_384Zko8ms2c_NnY,98
|
|
6
|
-
jobflow/core/flow.py,sha256=
|
|
7
|
-
jobflow/core/job.py,sha256=
|
|
6
|
+
jobflow/core/flow.py,sha256=ernJoGkts5hi3ruReJHDqioB5N2wpl6zjiwCQqhqGYM,34337
|
|
7
|
+
jobflow/core/job.py,sha256=9Tqv4u9Fc_3Ou1qBizHLvrgLB_WjVKcyjodlvMAnAMg,50319
|
|
8
8
|
jobflow/core/maker.py,sha256=WhsYw2wDNVIyAEeRUoikOQMzXzHuXfFVwXrJpwGCD1E,11162
|
|
9
|
-
jobflow/core/reference.py,sha256=
|
|
9
|
+
jobflow/core/reference.py,sha256=ZATBhaB9HDhPujsfscOcHw7nJf7QfKvozvII47_q1UU,18012
|
|
10
10
|
jobflow/core/schemas.py,sha256=Oi5-PnZpI8S9jSY7Q4f8H7xUybbRZDXlgugeVewVsrA,968
|
|
11
11
|
jobflow/core/state.py,sha256=IGJTtmpotDKEcgDEnsT5x20ZeyvQT68Mr3teTjkgYnM,709
|
|
12
|
-
jobflow/core/store.py,sha256=
|
|
12
|
+
jobflow/core/store.py,sha256=njeKh_F-sHGt-hk0lsETmgK9H1I7hAqCeJY4LzStAxo,27627
|
|
13
13
|
jobflow/managers/__init__.py,sha256=KkA5cVDe2os2_2aTa8eiB9SnkGLZNybcci-Lo4tbaWM,55
|
|
14
14
|
jobflow/managers/fireworks.py,sha256=xKPcL1BX59jnF6LpyEwcsxvCCShLqep-9UHRyv9FmYE,7146
|
|
15
15
|
jobflow/managers/local.py,sha256=iI1QI6Dg7P7C3vVd4Mt1xXAmcQ-o37Ox_gQRnafsBHE,6142
|
|
@@ -21,8 +21,8 @@ jobflow/utils/graph.py,sha256=kweAowEzv8ZGjJ9KZvJ-G5ueAqGPWbU0z7Xd4e-q8no,6596
|
|
|
21
21
|
jobflow/utils/log.py,sha256=4-_1OUSQ8I4Q6CgQ4pxNBeveBdpXla7nibZNF7Vk3pw,1110
|
|
22
22
|
jobflow/utils/uid.py,sha256=hNkpJ5AYhKd_sPWE_iGPcn6YD_AyizKX_swWksFr-_M,2537
|
|
23
23
|
jobflow/utils/uuid.py,sha256=m0fInOs1yklpavf7jId85luDOHjZqfIIUzEtd89Bk_s,440
|
|
24
|
-
jobflow-0.
|
|
25
|
-
jobflow-0.
|
|
26
|
-
jobflow-0.
|
|
27
|
-
jobflow-0.
|
|
28
|
-
jobflow-0.
|
|
24
|
+
jobflow-0.3.0.dist-info/licenses/LICENSE,sha256=jUEiENfZNQZh9RE9ixtUWgVkLRD85ScZ6iv1WREf19w,2418
|
|
25
|
+
jobflow-0.3.0.dist-info/METADATA,sha256=5lPrcdt01_rFBFjVSCxQpbHvH7N9JQwSOaODeG-Z6Po,10040
|
|
26
|
+
jobflow-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
27
|
+
jobflow-0.3.0.dist-info/top_level.txt,sha256=IanNooU88OupQPDrWnT0rbL3E27P2wEy7Jsfx9_j8zc,8
|
|
28
|
+
jobflow-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|