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 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 isinstance(config, dict):
1123
- # convert dict specification to a JobConfig but set the attributes
1124
- if attributes is None:
1125
- attributes = list(config.keys())
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
- # only update the specified attributes
1140
- attributes = [attributes] if isinstance(attributes, str) else attributes
1141
- for attr in attributes:
1142
- if not hasattr(self.config, attr):
1143
- raise ValueError(f"Unknown JobConfig attribute: {attr}")
1144
- setattr(self.config, attr, getattr(config, attr))
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
- if item not in schema_dict["properties"]:
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
- item_type = schema.model_fields[item].annotation
522
- if lenient_issubclass(item_type, BaseModel):
523
- subschema = item_type
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
- obj_type = Union[str, Enum, type[MSONable], list[Union[Enum, str, type[MSONable]]]]
25
- save_type = Optional[dict[str, obj_type]]
26
- load_type = Union[bool, dict[str, Union[bool, obj_type]]]
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
- locations.extend(find_key(doc, save_key, include_end=True))
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.2.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.57.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.0.1
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==2024.8.6; extra == "docs"
40
- Requires-Dist: ipython==9.3.0; extra == "docs"
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.7; extra == "docs"
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.5; extra == "tests"
50
- Requires-Dist: pytest-cov==6.1.1; extra == "tests"
51
- Requires-Dist: pytest==8.4.0; extra == "tests"
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.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"
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.5; extra == "strict"
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.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"
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=l7o10BaqEQWw5aZziWRg40PsIAgQ4lrlluXs9hIv2mg,570
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=Lg8T8Lz-7z7vD91mJl__zzFkia5cfYuk5IXubHCnKLc,30663
7
- jobflow/core/job.py,sha256=N0wagUYg_AUy32KAi1AvpvJRcUjPJ60rCS8dakgkhJY,48176
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=2xqNwyo57wFPLMLPprXa5h8f_cOWcyCHmDsfVEl5pRk,17101
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=PAtAhZTLU1b17OD66rqlo8_JP1p-IzH8c2WJwWd1Q2o,26997
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.2.0.dist-info/licenses/LICENSE,sha256=jUEiENfZNQZh9RE9ixtUWgVkLRD85ScZ6iv1WREf19w,2418
25
- jobflow-0.2.0.dist-info/METADATA,sha256=PFpdiC95t5alnZ5p7xiFpzCgMrsjXkvsFV1cs_LqsNI,10016
26
- jobflow-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
- jobflow-0.2.0.dist-info/top_level.txt,sha256=IanNooU88OupQPDrWnT0rbL3E27P2wEy7Jsfx9_j8zc,8
28
- jobflow-0.2.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5