jolt 0.9.355__tar.gz → 0.9.370__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 (100) hide show
  1. {jolt-0.9.355 → jolt-0.9.370}/PKG-INFO +2 -2
  2. {jolt-0.9.355 → jolt-0.9.370}/jolt/__init__.py +47 -0
  3. {jolt-0.9.355 → jolt-0.9.370}/jolt/cache.py +339 -159
  4. {jolt-0.9.355 → jolt-0.9.370}/jolt/cli.py +29 -98
  5. {jolt-0.9.355 → jolt-0.9.370}/jolt/config.py +14 -26
  6. {jolt-0.9.355 → jolt-0.9.370}/jolt/graph.py +27 -15
  7. {jolt-0.9.355 → jolt-0.9.370}/jolt/loader.py +141 -180
  8. {jolt-0.9.355 → jolt-0.9.370}/jolt/manifest.py +0 -46
  9. jolt-0.9.370/jolt/options.py +39 -0
  10. jolt-0.9.370/jolt/plugins/conan.py +479 -0
  11. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/docker.py +1 -1
  12. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/environ.py +11 -0
  13. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/gdb.py +6 -5
  14. jolt-0.9.370/jolt/plugins/linux.py +943 -0
  15. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/ninja-compdb.py +7 -6
  16. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/podman.py +4 -4
  17. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/scheduler.py +18 -14
  18. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/selfdeploy.py +1 -22
  19. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/strings.py +16 -6
  20. {jolt-0.9.355 → jolt-0.9.370}/jolt/scheduler.py +428 -138
  21. {jolt-0.9.355 → jolt-0.9.370}/jolt/tasks.py +23 -0
  22. {jolt-0.9.355 → jolt-0.9.370}/jolt/tools.py +15 -8
  23. jolt-0.9.370/jolt/version.py +1 -0
  24. {jolt-0.9.355 → jolt-0.9.370}/jolt.egg-info/PKG-INFO +2 -2
  25. {jolt-0.9.355 → jolt-0.9.370}/jolt.egg-info/SOURCES.txt +1 -2
  26. {jolt-0.9.355 → jolt-0.9.370}/jolt.egg-info/requires.txt +1 -1
  27. {jolt-0.9.355 → jolt-0.9.370}/setup.py +1 -1
  28. jolt-0.9.355/jolt/options.py +0 -16
  29. jolt-0.9.355/jolt/plugins/conan.py +0 -241
  30. jolt-0.9.355/jolt/plugins/debian.py +0 -338
  31. jolt-0.9.355/jolt/plugins/repo.py +0 -253
  32. jolt-0.9.355/jolt/version.py +0 -1
  33. {jolt-0.9.355 → jolt-0.9.370}/README.rst +0 -0
  34. {jolt-0.9.355 → jolt-0.9.370}/jolt/__main__.py +0 -0
  35. {jolt-0.9.355 → jolt-0.9.370}/jolt/bin/fstree-darwin-x86_64 +0 -0
  36. {jolt-0.9.355 → jolt-0.9.370}/jolt/bin/fstree-linux-x86_64 +0 -0
  37. {jolt-0.9.355 → jolt-0.9.370}/jolt/chroot.py +0 -0
  38. {jolt-0.9.355 → jolt-0.9.370}/jolt/colors.py +0 -0
  39. {jolt-0.9.355 → jolt-0.9.370}/jolt/common_pb2.py +0 -0
  40. {jolt-0.9.355 → jolt-0.9.370}/jolt/common_pb2_grpc.py +0 -0
  41. {jolt-0.9.355 → jolt-0.9.370}/jolt/error.py +0 -0
  42. {jolt-0.9.355 → jolt-0.9.370}/jolt/expires.py +0 -0
  43. {jolt-0.9.355 → jolt-0.9.370}/jolt/filesystem.py +0 -0
  44. {jolt-0.9.355 → jolt-0.9.370}/jolt/hooks.py +0 -0
  45. {jolt-0.9.355 → jolt-0.9.370}/jolt/influence.py +0 -0
  46. {jolt-0.9.355 → jolt-0.9.370}/jolt/inspection.py +0 -0
  47. {jolt-0.9.355 → jolt-0.9.370}/jolt/log.py +0 -0
  48. {jolt-0.9.355 → jolt-0.9.370}/jolt/pkgs/__init__.py +0 -0
  49. {jolt-0.9.355 → jolt-0.9.370}/jolt/pkgs/golang.py +0 -0
  50. {jolt-0.9.355 → jolt-0.9.370}/jolt/pkgs/nodejs.py +0 -0
  51. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/__init__.py +0 -0
  52. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/alias.py +0 -0
  53. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/allure.py +0 -0
  54. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/autoweight.py +0 -0
  55. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/cache.py +0 -0
  56. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/cmake.py +0 -0
  57. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/cxx.py +0 -0
  58. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/cxxinfo.py +0 -0
  59. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/dashboard.py +0 -0
  60. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/email.py +0 -0
  61. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/email.xslt +0 -0
  62. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/gerrit.py +0 -0
  63. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/git.py +0 -0
  64. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/golang.py +0 -0
  65. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/googletest.py +0 -0
  66. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/http.py +0 -0
  67. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/junit.py +0 -0
  68. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/logstash.py +0 -0
  69. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/ninja.py +0 -0
  70. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/nodejs.py +0 -0
  71. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/paths.py +0 -0
  72. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/python.py +0 -0
  73. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/__init__.py +0 -0
  74. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/administration_pb2.py +0 -0
  75. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/administration_pb2_grpc.py +0 -0
  76. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/log_pb2.py +0 -0
  77. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/log_pb2_grpc.py +0 -0
  78. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/scheduler_pb2.py +0 -0
  79. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/scheduler_pb2_grpc.py +0 -0
  80. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/worker_pb2.py +0 -0
  81. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/remote_execution/worker_pb2_grpc.py +0 -0
  82. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/report.py +0 -0
  83. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/symlinks.py +0 -0
  84. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/telemetry.py +0 -0
  85. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/timeline.py +0 -0
  86. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/volume.py +0 -0
  87. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/yaml-ninja.py +0 -0
  88. {jolt-0.9.355 → jolt-0.9.370}/jolt/plugins/yamltask.py +0 -0
  89. {jolt-0.9.355 → jolt-0.9.370}/jolt/templates/cxxexecutable.cmake.template +0 -0
  90. {jolt-0.9.355 → jolt-0.9.370}/jolt/templates/cxxlibrary.cmake.template +0 -0
  91. {jolt-0.9.355 → jolt-0.9.370}/jolt/templates/export.sh.template +0 -0
  92. {jolt-0.9.355 → jolt-0.9.370}/jolt/templates/timeline.html.template +0 -0
  93. {jolt-0.9.355 → jolt-0.9.370}/jolt/timer.py +0 -0
  94. {jolt-0.9.355 → jolt-0.9.370}/jolt/utils.py +0 -0
  95. {jolt-0.9.355 → jolt-0.9.370}/jolt/version_utils.py +0 -0
  96. {jolt-0.9.355 → jolt-0.9.370}/jolt/xmldom.py +0 -0
  97. {jolt-0.9.355 → jolt-0.9.370}/jolt.egg-info/dependency_links.txt +0 -0
  98. {jolt-0.9.355 → jolt-0.9.370}/jolt.egg-info/entry_points.txt +0 -0
  99. {jolt-0.9.355 → jolt-0.9.370}/jolt.egg-info/top_level.txt +0 -0
  100. {jolt-0.9.355 → jolt-0.9.370}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jolt
3
- Version: 0.9.355
3
+ Version: 0.9.370
4
4
  Summary: A task executor
5
5
  Home-page: https://github.com/srand/jolt
6
6
  Author: Robert Andersson
@@ -58,7 +58,7 @@ Requires-Dist: zstandard==0.23.0
58
58
  Provides-Extra: allure
59
59
  Requires-Dist: allure-python-commons; extra == "allure"
60
60
  Provides-Extra: conan
61
- Requires-Dist: conan<2.0; extra == "conan"
61
+ Requires-Dist: conan>=2.0; extra == "conan"
62
62
  Provides-Extra: dev
63
63
  Requires-Dist: check-manifest; extra == "dev"
64
64
  Provides-Extra: doc
@@ -93,3 +93,50 @@ def include(joltfile, joltdir=None):
93
93
  except Exception as e:
94
94
  from jolt.error import raise_error
95
95
  raise_error("Failed to load '{0}': {1}", joltfile, str(e))
96
+
97
+
98
+ def require_resource(name: str, ):
99
+ """ Require a resource task.
100
+
101
+ Args:
102
+ name (str): The name of the resource task.
103
+
104
+ Example:
105
+
106
+ .. code-block:: python
107
+
108
+ from jolt import require_resource
109
+
110
+ require_resource("git:url=https://github.com/srand/jolt.git")
111
+
112
+ """
113
+ from jolt.tasks import TaskRegistry
114
+ registry = TaskRegistry.get()
115
+ registry.require_workspace_resource(name)
116
+
117
+
118
+ def require_version(verstr: str):
119
+ """ Require a specific version of Jolt.
120
+
121
+ Args:
122
+ verstr (str): The version to require. Must be a string.
123
+ Operators such as ``>=`` and ``<=`` are supported.
124
+
125
+ Example:
126
+
127
+ .. code-block:: python
128
+
129
+ from jolt import require_version
130
+
131
+ require_version("1.0.0")
132
+
133
+ """
134
+ from jolt.error import raise_error_if
135
+ from jolt.version_utils import requirement, version
136
+
137
+ req = requirement(verstr)
138
+ ver = version(__version__)
139
+ raise_error_if(
140
+ not req.satisfied(ver),
141
+ "This project requires Jolt version {} (running {})",
142
+ req, __version__)
@@ -33,194 +33,122 @@ def locked(func):
33
33
  return _f
34
34
 
35
35
 
36
- class StorageProvider(object):
37
- def download(self, artifact, force=False):
38
- return False
39
-
40
- def download_enabled(self):
41
- return True
42
-
43
- def upload(self, artifact, force=False):
44
- return False
45
-
46
- def upload_enabled(self):
47
- return True
48
-
49
- def location(self, artifact):
50
- return '' # URL
51
-
52
- def availability(self, artifacts):
53
- # Ensure artifacts is a list
54
- artifacts = utils.as_list(artifacts)
55
-
56
- present = set()
57
- missing = set()
58
-
59
- for artifact in artifacts:
60
- if self.location(artifact):
61
- present.add(artifact)
62
- else:
63
- missing.add(artifact)
64
-
65
- return list(present), list(missing)
66
-
67
-
68
- class StorageProviderFactory(StorageProvider):
69
- def create(self):
70
- pass
71
-
72
-
73
- def RegisterStorage(cls):
74
- ArtifactCache.storage_provider_factories.append(cls)
75
-
76
-
77
- class ArtifactAttributeSet(object):
78
- def __init__(self):
79
- super(ArtifactAttributeSet, self).__setattr__("_attributes", {})
80
-
81
- def _get_attributes(self):
82
- return self._attributes
83
-
84
- def __getattr__(self, name):
85
- attributes = self._get_attributes()
86
- if name not in attributes:
87
- attributes[name] = self.create(name)
88
- return attributes[name]
89
-
90
- def __setattr__(self, name, value):
91
- attributes = self._get_attributes()
92
- if name not in attributes:
93
- attributes[name] = self.create(name)
94
- attributes[name].set_value(value)
95
- return attributes[name]
96
-
97
- def __dict__(self):
98
- return {key: str(value) for key, value in self.items()}
99
-
100
- def items(self):
101
- return self._get_attributes().items()
102
-
103
- def apply(self, task, artifact):
104
- for _, value in self.items():
105
- value.apply(task, artifact)
106
-
107
- def apply_deps(self, task, deps):
108
- pass
109
-
110
- def unapply(self, task, artifact):
111
- for _, value in self.items():
112
- value.unapply(task, artifact)
113
-
114
- def unapply_deps(self, task, deps):
115
- pass
116
-
117
- def visit(self, task, artifact, visitor):
118
- for _, value in self.items():
119
- value.visit(task, artifact, visitor)
120
-
121
-
122
36
  class ArtifactAttributeSetRegistry(object):
123
- providers = []
37
+ """
38
+ Registry for providers of artifact attribute sets.
39
+ """
40
+
41
+ providers = [] # List of objects that implement ArtifactAttributeSetProvider
124
42
 
125
43
  @staticmethod
126
44
  def create_all(artifact):
45
+ """ Create all artifact attribute sets. """
127
46
  for provider in ArtifactAttributeSetRegistry.providers:
128
47
  provider().create(artifact)
129
48
 
130
49
  @staticmethod
131
50
  def parse_all(artifact, content):
51
+ """ Parse all artifact attribute sets. """
132
52
  for provider in ArtifactAttributeSetRegistry.providers:
133
53
  provider().parse(artifact, content)
134
54
 
135
55
  @staticmethod
136
56
  def format_all(artifact, content):
57
+ """ Format all artifact attribute sets. """
137
58
  for provider in ArtifactAttributeSetRegistry.providers:
138
59
  provider().format(artifact, content)
139
60
 
140
61
  @staticmethod
141
62
  def apply_all(task, artifact):
63
+ """ Apply all artifact attribute sets. """
142
64
  for provider in ArtifactAttributeSetRegistry.providers:
143
65
  provider().apply(task, artifact)
144
66
 
145
- @staticmethod
146
- def apply_all_deps(task, deps):
147
- for provider in ArtifactAttributeSetRegistry.providers:
148
- provider().apply_deps(task, deps)
149
-
150
67
  @staticmethod
151
68
  def unapply_all(task, artifact):
69
+ """ Unapply all artifact attribute sets. """
152
70
  for provider in ArtifactAttributeSetRegistry.providers:
153
71
  provider().unapply(task, artifact)
154
72
 
155
- @staticmethod
156
- def unapply_all_deps(task, deps):
157
- for provider in ArtifactAttributeSetRegistry.providers:
158
- provider().unapply_deps(task, deps)
159
-
160
73
  @staticmethod
161
74
  def visit_all(task, artifact, visitor):
75
+ """ Visit all artifact attribute sets. """
162
76
  for provider in ArtifactAttributeSetRegistry.providers:
163
77
  provider().visit(task, artifact, visitor)
164
78
 
165
79
 
166
- def visit_artifact(task, artifact, visitor):
167
- ArtifactAttributeSetRegistry.visit_all(task, artifact, visitor)
168
-
169
-
170
- class ArtifactAttributeSetProvider(object):
171
- @staticmethod
172
- def Register(cls):
173
- ArtifactAttributeSetRegistry.providers.append(cls)
174
-
175
- def create(self, artifact):
176
- raise NotImplementedError()
177
-
178
- def parse(self, artifact, content):
179
- raise NotImplementedError()
180
-
181
- def format(self, artifact, content):
182
- raise NotImplementedError()
183
-
184
- def apply(self, task, artifact):
185
- pass
80
+ class ArtifactAttribute(object):
81
+ """
82
+ An artifact attribute.
186
83
 
187
- def apply_deps(self, task, deps):
188
- pass
84
+ An artifact attribute is a key-value pair that can be set and retrieved
85
+ from an artifact attribute set. Attributes are used to store metadata and other
86
+ information that is associated with an artifact. They communicate information
87
+ between tasks and store information that is used by tasks when they consume an artifact.
189
88
 
190
- def unapply(self, task, artifact):
191
- pass
192
-
193
- def unapply_deps(self, task, deps):
194
- pass
89
+ Artifact attributes can also perform actions when the artifact is consumed.
195
90
 
196
- def visit(self, task, artifact, visitor):
197
- pass
198
-
199
-
200
- class ArtifactAttribute(object):
91
+ """
201
92
  def __init__(self, name):
202
93
  self._name = name
203
94
 
204
95
  def get_name(self):
96
+ """ Get the name of the attribute. """
205
97
  return self._name
206
98
 
207
99
  def set_value(self, value, expand=True):
100
+ """
101
+ Set the value of the attribute.
102
+
103
+ Must be implemented by subclasses.
104
+
105
+ Args:
106
+ value: The value to set.
107
+ expand: If True, the value is macro expanded using the tools.expand() method.
108
+ """
208
109
  raise NotImplementedError()
209
110
 
210
111
  def get_value(self):
112
+ """
113
+ Get the value of the attribute.
114
+
115
+ Must be implemented by subclasses.
116
+ """
211
117
  raise NotImplementedError()
212
118
 
213
119
  def apply(self, task, artifact):
120
+ """
121
+ Perform an action when the artifact is being used.
122
+
123
+ Args:
124
+ task (Task): The task that is using the artifact.
125
+ artifact (Artifact): The artifact that is being used.
126
+
127
+ """
214
128
  pass
215
129
 
216
130
  def unapply(self, task, artifact):
131
+ """
132
+ Undo an action when the artifact is no longer being used.
133
+
134
+ Args:
135
+ task (Task): The task that is no longer using the artifact.
136
+ artifact (Artifact): The artifact that is no longer being used.
137
+ """
217
138
  pass
218
139
 
219
- def __str__(self):
140
+ def __str__(self) -> str:
141
+ """
142
+ Get a string representation of the attribute.
143
+
144
+ Must be implemented by subclasses.
145
+ """
220
146
  raise NotImplementedError()
221
147
 
222
148
 
223
149
  class ArtifactStringAttribute(ArtifactAttribute):
150
+ """ An artifact attribute that stores a string value. """
151
+
224
152
  def __init__(self, artifact, name):
225
153
  self._artifact = artifact
226
154
  self._name = name
@@ -235,17 +163,13 @@ class ArtifactStringAttribute(ArtifactAttribute):
235
163
  def get_value(self):
236
164
  return self._value
237
165
 
238
- def apply(self, task, artifact):
239
- pass
240
-
241
- def unapply(self, task, artifact):
242
- pass
243
-
244
- def __str__(self):
166
+ def __str__(self) -> str:
245
167
  return str(self._value)
246
168
 
247
169
 
248
170
  class ArtifactListAttribute(ArtifactAttribute):
171
+ """ An artifact attribute that stores a list of values. """
172
+
249
173
  def __init__(self, artifact, name):
250
174
  self._artifact = artifact
251
175
  self._name = name
@@ -257,6 +181,9 @@ class ArtifactListAttribute(ArtifactAttribute):
257
181
  def __getslice__(self, i, j):
258
182
  return self._value[i:j]
259
183
 
184
+ def __len__(self):
185
+ return len(self._value)
186
+
260
187
  def get_name(self):
261
188
  return self._name
262
189
 
@@ -287,23 +214,13 @@ class ArtifactListAttribute(ArtifactAttribute):
287
214
  def count(self):
288
215
  return len(self.items())
289
216
 
290
- def apply(self, task, artifact):
291
- pass
292
-
293
- def unapply(self, task, artifact):
294
- pass
295
-
296
217
 
297
218
  class ArtifactFileAttribute(object):
219
+ """ An attribute that stores a list of source and destination path tuples for files collected into the artifact. """
220
+
298
221
  def __init__(self):
299
222
  self._files = []
300
223
 
301
- def apply(self, task, artifact):
302
- pass
303
-
304
- def unapply(self, task, artifact):
305
- pass
306
-
307
224
  def append(self, src, dst):
308
225
  self._files.append((fs.as_posix(src), fs.as_posix(dst)))
309
226
 
@@ -314,8 +231,129 @@ class ArtifactFileAttribute(object):
314
231
  return self._files
315
232
 
316
233
 
234
+ class ArtifactAttributeSet(object):
235
+ """
236
+ A set of artifact attributes.
237
+
238
+ An attribute set is a collection of attributes. Each attribute is
239
+ accessed using the attribute name as an attribute of the set. For
240
+ example, to access an attribute named 'version' in an attribute set
241
+ named 'strings', you would write:
242
+
243
+ .. code-block:: python
244
+
245
+ artifact.strings.version = "1.0"
246
+
247
+ """
248
+
249
+ def __init__(self):
250
+ super(ArtifactAttributeSet, self).__setattr__("_attributes", {})
251
+
252
+ def _get_attributes(self):
253
+ return self._attributes
254
+
255
+ def __getattr__(self, name) -> ArtifactAttribute:
256
+ """
257
+ Get or create an attribute by name.
258
+
259
+ Args:
260
+ name (str): The name of the attribute.
261
+
262
+ Returns:
263
+ An attribute object.
264
+ """
265
+ attributes = self._get_attributes()
266
+ if name not in attributes:
267
+ attributes[name] = self.create(name)
268
+ return attributes[name]
269
+
270
+ def __setattr__(self, name, value):
271
+ """
272
+ Set an attribute by name.
273
+
274
+ Args:
275
+ name (str): The name of the attribute.
276
+ value: The value to set.
277
+ """
278
+ attributes = self._get_attributes()
279
+ if name not in attributes:
280
+ attributes[name] = self.create(name)
281
+ attributes[name].set_value(value)
282
+ return attributes[name]
283
+
284
+ def __dict__(self):
285
+ """ Get a dictionary representation of the attribute set. """
286
+ return {key: str(value) for key, value in self.items()}
287
+
288
+ def items(self):
289
+ """ Get a list of tuples containing the attribute name and value. """
290
+ return self._get_attributes().items()
291
+
292
+ def apply(self, task, artifact):
293
+ """ Perform attribute actions when the artifact is being used. """
294
+ for _, value in self.items():
295
+ value.apply(task, artifact)
296
+
297
+ def unapply(self, task, artifact):
298
+ """ Undo attribute actions when the artifact is no longer being used. """
299
+ for _, value in self.items():
300
+ value.unapply(task, artifact)
301
+
302
+ def visit(self, task, artifact, visitor):
303
+ """ Visit all attributes in the set. """
304
+ for _, value in self.items():
305
+ value.visit(task, artifact, visitor)
306
+
307
+
308
+ class ArtifactAttributeSetProvider(object):
309
+ """ Base class for artifact attribute set providers.
310
+
311
+ An artifact attribute set provider is a factory for creating and managing
312
+ attribute sets in an artifact.
313
+ """
314
+
315
+ @staticmethod
316
+ def Register(cls):
317
+ """ Decorator for registering a provider class. """
318
+ ArtifactAttributeSetRegistry.providers.append(cls)
319
+
320
+ def create(self, artifact):
321
+ """ Create an attribute set for an artifact. """
322
+ raise NotImplementedError()
323
+
324
+ def parse(self, artifact, content):
325
+ """
326
+ Parse an attribute set from a dictionary.
327
+
328
+ The dictionary is loaded from a JSON file embedded in the artifact.
329
+ """
330
+ raise NotImplementedError()
331
+
332
+ def format(self, artifact, content):
333
+ """
334
+ Format an attribute set to a dictionary.
335
+
336
+ The dictionary is saved to a JSON file embedded in the artifact.
337
+ """
338
+ raise NotImplementedError()
339
+
340
+ def apply(self, task, artifact):
341
+ """ Perform actions when the artifact is being used. """
342
+ pass
343
+
344
+ def unapply(self, task, artifact):
345
+ """ Undo actions when the artifact is no longer being used. """
346
+ pass
347
+
348
+ def visit(self, task, artifact, visitor):
349
+ """ Visit all attributes in the set. """
350
+ pass
351
+
352
+
317
353
  @ArtifactAttributeSetProvider.Register
318
354
  class ArtifactFileAttributeProvider(ArtifactAttributeSetProvider):
355
+ """ Provider for the artifact 'files' attribute set. """
356
+
319
357
  def create(self, artifact):
320
358
  setattr(artifact, "files", ArtifactFileAttribute())
321
359
 
@@ -329,22 +367,19 @@ class ArtifactFileAttributeProvider(ArtifactAttributeSetProvider):
329
367
  def format(self, artifact, content):
330
368
  content["files"] = [{"src": src, "dst": dst} for src, dst in artifact.files.items()]
331
369
 
332
- def apply(self, task, artifact):
333
- pass
334
370
 
335
- def unapply(self, task, artifact):
336
- pass
337
-
338
- def visit(self, task, artifact, visitor):
339
- pass
371
+ def visit_artifact(task, artifact, visitor):
372
+ ArtifactAttributeSetRegistry.visit_all(task, artifact, visitor)
340
373
 
341
374
 
342
375
  def json_serializer(obj):
376
+ """ JSON serializer for datetime objects. """
343
377
  if isinstance(obj, datetime):
344
378
  return dict(type="datetime", value=obj.strftime("%Y-%m-%d %H:%M:%S.%f"))
345
379
 
346
380
 
347
381
  def json_deserializer(dct):
382
+ """ JSON deserializer for datetime objects. """
348
383
  if dct.get("type") == "datetime":
349
384
  return datetime.strptime(dct["value"], "%Y-%m-%d %H:%M:%S.%f")
350
385
  return dct
@@ -880,6 +915,16 @@ class Artifact(object):
880
915
 
881
916
 
882
917
  class ArtifactToolsProxy(object):
918
+ """
919
+ An artifact proxy that uses a specific tools object.
920
+
921
+ Used when artifacts are consumed by tasks. The proxy allows the
922
+ task to access the artifact's methods and attributes using the
923
+ task's own tools object. This is useful when the consumer task
924
+ wishes to copy files, read files, etc, using the current working
925
+ directory and environment of the task.
926
+ """
927
+
883
928
  def __init__(self, artifact, tools):
884
929
  self._artifact = artifact
885
930
  self._tools = tools
@@ -945,10 +990,8 @@ class Context(object):
945
990
  self._artifacts_index[artifact.name + "@" + dep.short_qualified_name] = artifact
946
991
  artifact.apply()
947
992
  ArtifactAttributeSetRegistry.apply_all(self._node.task, artifact)
948
- ArtifactAttributeSetRegistry.apply_all_deps(self._node.task, self)
949
993
  except (Exception, KeyboardInterrupt) as e:
950
994
  # Rollback all attributes/resources except the last failing one
951
- ArtifactAttributeSetRegistry.unapply_all_deps(self._node.task, self)
952
995
  for name, artifact in reversed(list(self._artifacts.items())[:-1]):
953
996
  with utils.ignore_exception():
954
997
  ArtifactAttributeSetRegistry.unapply_all(self._node.task, artifact)
@@ -957,7 +1000,6 @@ class Context(object):
957
1000
  return self
958
1001
 
959
1002
  def __exit__(self, type, value, tb):
960
- ArtifactAttributeSetRegistry.unapply_all_deps(self._node.task, self)
961
1003
  for name, artifact in reversed(self._artifacts.items()):
962
1004
  ArtifactAttributeSetRegistry.unapply_all(self._node.task, artifact)
963
1005
  artifact.unapply()
@@ -1025,6 +1067,144 @@ class PidProvider(object):
1025
1067
  return pid
1026
1068
 
1027
1069
 
1070
+ class StorageProvider(object):
1071
+ """
1072
+ Base class for remote artifact storage providers.
1073
+
1074
+ A storage provider is responsible for uploading and downloading
1075
+ artifacts to and from a remote storage location. The storage
1076
+ location can be a file system path, a cloud storage service, or
1077
+ any other type of storage.
1078
+
1079
+ """
1080
+
1081
+ def download(self, artifact: Artifact, force: bool = False) -> bool:
1082
+ """
1083
+ Download an artifact from the storage location.
1084
+
1085
+ The should be downloaded to the path returned by the artifact's
1086
+ :func:`~jolt.Artifact.get_archive_path` method. The downloaded artifact
1087
+ must be in the format specified by DEFAULT_ARCHIVE_TYPE.
1088
+
1089
+ The download should be retried if it fails due to network issues.
1090
+ The method may raise an exception on errors.
1091
+
1092
+ Args:
1093
+ artifact (Artifact): The artifact to download.
1094
+ force (bool, optional): If True, the download should be forced,
1095
+ even if the artifact is already present locally, or if the
1096
+ download is disabled. The default is False.
1097
+
1098
+ Returns:
1099
+ bool: True if the download was successful, False otherwise.
1100
+
1101
+ """
1102
+ return False
1103
+
1104
+ def download_enabled(self) -> bool:
1105
+ """ Return True if downloading is enabled. Default is True. """
1106
+ return True
1107
+
1108
+ def upload(self, artifact: Artifact, force: bool = False) -> bool:
1109
+ """
1110
+ Upload an artifact to the storage location.
1111
+
1112
+ The artifact to be uploaded is located at the path returned by
1113
+ the artifact's :func:`~jolt.Artifact.get_archive_path` method. The
1114
+ uploaded artifact is in the format specified by DEFAULT_ARCHIVE_TYPE.
1115
+ The provider may choose to upload the artifact using a different
1116
+ format, but it must be able to download the artifact in the
1117
+ DEFAULT_ARCHIVE_TYPE format.
1118
+
1119
+ The upload should be retried if it fails due to network issues.
1120
+ The method may raise an exception on errors.
1121
+
1122
+ Args:
1123
+ artifact (Artifact): The artifact to upload.
1124
+ force (bool, optional): If True, the upload should be forced,
1125
+ even if the artifact is already present remotely, or if the
1126
+ upload is disabled. The default is False.
1127
+
1128
+ Returns:
1129
+ bool: True if the upload was successful, False otherwise.
1130
+
1131
+ """
1132
+ return False
1133
+
1134
+ def upload_enabled(self) -> bool:
1135
+ """ Return True if uploading is enabled. Default is True. """
1136
+ return True
1137
+
1138
+ def location(self, artifact) -> str:
1139
+ """
1140
+ Return the URL of the artifact in the storage location.
1141
+
1142
+ This method is sometimes used to identify if an artifact is
1143
+ present in the storage location. The URL should point to the
1144
+ artifact if present, or an empty string if the artifact is
1145
+ absent.
1146
+
1147
+ Args:
1148
+ artifact (Artifact): The artifact to locate.
1149
+ """
1150
+ return '' # URL
1151
+
1152
+ def availability(self, artifacts: list) -> tuple:
1153
+ """
1154
+ Check the availability of a list of artifacts.
1155
+
1156
+ This method is used to determine which artifacts are present in the
1157
+ storage location. The method should return a tuple of two lists:
1158
+ the first list contains the artifacts that are present, and the
1159
+ second list contains the artifacts that are missing.
1160
+
1161
+ The default implementation of this method calls the :func:`~jolt.StorageProvider.location`
1162
+ method for each artifact in the list. Subclasses may override this
1163
+ method to provide a more efficient implementation.
1164
+
1165
+ Args:
1166
+ artifacts (list): A list of artifacts to check.
1167
+
1168
+ Returns:
1169
+ tuple: A tuple of two lists: the first list contains the artifacts
1170
+ that are present, and the second list contains the artifacts
1171
+ that are missing.
1172
+
1173
+ """
1174
+ # Ensure artifacts is a list
1175
+ artifacts = utils.as_list(artifacts)
1176
+
1177
+ present = set()
1178
+ missing = set()
1179
+
1180
+ for artifact in artifacts:
1181
+ if self.location(artifact):
1182
+ present.add(artifact)
1183
+ else:
1184
+ missing.add(artifact)
1185
+
1186
+ return list(present), list(missing)
1187
+
1188
+
1189
+ class StorageProviderFactory(StorageProvider):
1190
+ """ A factory for store providers. """
1191
+
1192
+ def create(self) -> StorageProvider:
1193
+ """
1194
+ Create a new storage provider.
1195
+
1196
+ This method should return a new instance of a storage provider,
1197
+ which must be a subclass of :class:`~jolt.StorageProvider`.
1198
+
1199
+ """
1200
+ pass
1201
+
1202
+
1203
+ def RegisterStorage(cls):
1204
+ """ Decorator used to register a storage provider factory. """
1205
+ ArtifactCache.storage_provider_factories.append(cls)
1206
+
1207
+
1028
1208
  @utils.Singleton
1029
1209
  class ArtifactCache(StorageProvider):
1030
1210
  """