DIRACCommon 9.0.0a66__tar.gz → 9.0.0a67__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.
- {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/PKG-INFO +9 -4
- {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/README.md +6 -3
- {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/pyproject.toml +2 -0
- diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/ClassAd/ClassAdLight.py +295 -0
- diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/ClassAd/__init__.py +1 -0
- diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/JDL.py +199 -0
- diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/List.py +127 -0
- {diraccommon-9.0.0a66/src/DIRACCommon/Utils → diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities}/ReturnValues.py +1 -1
- diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/StateMachine.py +185 -0
- diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/TimeUtilities.py +259 -0
- diraccommon-9.0.0a67/src/DIRACCommon/Core/__init__.py +1 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Client/JobState/JobManifest.py +235 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Client/JobState/__init__.py +0 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Client/JobStatus.py +95 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Client/__init__.py +1 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/DB/JobDBUtils.py +170 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/DB/__init__.py +1 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Utilities/JobModel.py +236 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Utilities/JobStatusUtility.py +93 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Utilities/ParametricJob.py +179 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Utilities/__init__.py +1 -0
- diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/__init__.py +1 -0
- diraccommon-9.0.0a67/tests/Core/Utilities/ClassAd/__init__.py +0 -0
- diraccommon-9.0.0a67/tests/Core/Utilities/ClassAd/test_ClassAdLight.py +69 -0
- diraccommon-9.0.0a67/tests/Core/Utilities/__init__.py +0 -0
- {diraccommon-9.0.0a66/tests/Utils → diraccommon-9.0.0a67/tests/Core/Utilities}/test_DErrno.py +1 -1
- diraccommon-9.0.0a67/tests/Core/Utilities/test_JDL.py +113 -0
- diraccommon-9.0.0a67/tests/Core/Utilities/test_List.py +150 -0
- {diraccommon-9.0.0a66/tests/Utils → diraccommon-9.0.0a67/tests/Core/Utilities}/test_ReturnValues.py +7 -1
- diraccommon-9.0.0a67/tests/Core/Utilities/test_TimeUtilities.py +87 -0
- diraccommon-9.0.0a67/tests/Core/__init__.py +0 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Client/JobState/__init__.py +1 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Client/JobState/test_JobManifest.py +129 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Client/__init__.py +1 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/DB/__init__.py +0 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/DB/test_JobDBUtils.py +73 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Utilities/__init__.py +0 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Utilities/test_JobModel.py +264 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Utilities/test_JobStatusUtility.py +164 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Utilities/test_ParametricJob.py +141 -0
- diraccommon-9.0.0a67/tests/WorkloadManagementSystem/__init__.py +0 -0
- {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/.gitignore +0 -0
- {diraccommon-9.0.0a66/src/DIRACCommon/Utils → diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities}/DErrno.py +0 -0
- {diraccommon-9.0.0a66/src/DIRACCommon/Utils → diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities}/__init__.py +0 -0
- {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/src/DIRACCommon/__init__.py +0 -0
- {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DIRACCommon
|
|
3
|
-
Version: 9.0.
|
|
3
|
+
Version: 9.0.0a67
|
|
4
4
|
Summary: Stateless utilities extracted from DIRAC for use by DiracX and other projects
|
|
5
5
|
Project-URL: Homepage, https://github.com/DIRACGrid/DIRAC
|
|
6
6
|
Project-URL: Documentation, https://dirac.readthedocs.io/
|
|
@@ -14,6 +14,8 @@ Classifier: Programming Language :: Python :: 3
|
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering
|
|
15
15
|
Classifier: Topic :: System :: Distributed Computing
|
|
16
16
|
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: diraccfg
|
|
18
|
+
Requires-Dist: pydantic>=2.0.0
|
|
17
19
|
Requires-Dist: typing-extensions>=4.0.0
|
|
18
20
|
Provides-Extra: testing
|
|
19
21
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'testing'
|
|
@@ -30,8 +32,11 @@ This package solves the circular dependency issue where DiracX needs DIRAC utili
|
|
|
30
32
|
|
|
31
33
|
## Contents
|
|
32
34
|
|
|
33
|
-
- `DIRACCommon.
|
|
34
|
-
- `DIRACCommon.
|
|
35
|
+
- `DIRACCommon.Core.Utilities.ReturnValues`: DIRAC's S_OK/S_ERROR return value system
|
|
36
|
+
- `DIRACCommon.Core.Utilities.DErrno`: DIRAC error codes and utilities
|
|
37
|
+
- `DIRACCommon.Core.Utilities.ClassAd.ClassAdLight`: JDL parsing utilities
|
|
38
|
+
- `DIRACCommon.WorkloadManagementSystem.DB.JobDBUtils`: Job database utilities
|
|
39
|
+
- `DIRACCommon.WorkloadManagementSystem.Utilities.ParametricJob`: Parametric job utilities
|
|
35
40
|
|
|
36
41
|
## Installation
|
|
37
42
|
|
|
@@ -42,7 +47,7 @@ pip install DIRACCommon
|
|
|
42
47
|
## Usage
|
|
43
48
|
|
|
44
49
|
```python
|
|
45
|
-
from DIRACCommon.
|
|
50
|
+
from DIRACCommon.Core.Utilities.ReturnValues import S_OK, S_ERROR
|
|
46
51
|
|
|
47
52
|
def my_function():
|
|
48
53
|
if success:
|
|
@@ -8,8 +8,11 @@ This package solves the circular dependency issue where DiracX needs DIRAC utili
|
|
|
8
8
|
|
|
9
9
|
## Contents
|
|
10
10
|
|
|
11
|
-
- `DIRACCommon.
|
|
12
|
-
- `DIRACCommon.
|
|
11
|
+
- `DIRACCommon.Core.Utilities.ReturnValues`: DIRAC's S_OK/S_ERROR return value system
|
|
12
|
+
- `DIRACCommon.Core.Utilities.DErrno`: DIRAC error codes and utilities
|
|
13
|
+
- `DIRACCommon.Core.Utilities.ClassAd.ClassAdLight`: JDL parsing utilities
|
|
14
|
+
- `DIRACCommon.WorkloadManagementSystem.DB.JobDBUtils`: Job database utilities
|
|
15
|
+
- `DIRACCommon.WorkloadManagementSystem.Utilities.ParametricJob`: Parametric job utilities
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
@@ -20,7 +23,7 @@ pip install DIRACCommon
|
|
|
20
23
|
## Usage
|
|
21
24
|
|
|
22
25
|
```python
|
|
23
|
-
from DIRACCommon.
|
|
26
|
+
from DIRACCommon.Core.Utilities.ReturnValues import S_OK, S_ERROR
|
|
24
27
|
|
|
25
28
|
def my_function():
|
|
26
29
|
if success:
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
""" ClassAd Class - a light purely Python representation of the
|
|
2
|
+
Condor ClassAd library.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ClassAd:
|
|
7
|
+
def __init__(self, jdl):
|
|
8
|
+
"""ClassAd constructor from a JDL string"""
|
|
9
|
+
self.contents = {}
|
|
10
|
+
result = self.__analyse_jdl(jdl)
|
|
11
|
+
if result:
|
|
12
|
+
self.contents = result
|
|
13
|
+
|
|
14
|
+
def __analyse_jdl(self, jdl, index=0):
|
|
15
|
+
"""Analyse one [] jdl enclosure"""
|
|
16
|
+
|
|
17
|
+
jdl = jdl.strip()
|
|
18
|
+
|
|
19
|
+
# Strip all the blanks first
|
|
20
|
+
# temp = jdl.replace(' ','').replace('\n','')
|
|
21
|
+
temp = jdl
|
|
22
|
+
|
|
23
|
+
result = {}
|
|
24
|
+
|
|
25
|
+
if temp[0] != "[" or temp[-1] != "]":
|
|
26
|
+
print("Invalid JDL: it should start with [ and end with ]")
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
# Parse the jdl string now
|
|
30
|
+
body = temp[1:-1]
|
|
31
|
+
index = 0
|
|
32
|
+
namemode = 1
|
|
33
|
+
valuemode = 0
|
|
34
|
+
while index < len(body):
|
|
35
|
+
if namemode:
|
|
36
|
+
ind = body.find("=", index)
|
|
37
|
+
if ind != -1:
|
|
38
|
+
name = body[index:ind]
|
|
39
|
+
index = ind + 1
|
|
40
|
+
valuemode = 1
|
|
41
|
+
namemode = 0
|
|
42
|
+
else:
|
|
43
|
+
break
|
|
44
|
+
elif valuemode:
|
|
45
|
+
ind1 = body.find("[", index)
|
|
46
|
+
ind2 = body.find(";", index)
|
|
47
|
+
if ind1 != -1 and ind1 < ind2:
|
|
48
|
+
value, newind = self.__find_subjdl(body, ind1)
|
|
49
|
+
elif ind1 == -1 and ind2 == -1:
|
|
50
|
+
value = body[index:]
|
|
51
|
+
newind = len(body)
|
|
52
|
+
else:
|
|
53
|
+
if index == ind2:
|
|
54
|
+
return {}
|
|
55
|
+
else:
|
|
56
|
+
value = body[index:ind2]
|
|
57
|
+
newind = ind2 + 1
|
|
58
|
+
|
|
59
|
+
result[name.strip()] = value.strip().replace("\n", "")
|
|
60
|
+
index = newind
|
|
61
|
+
valuemode = 0
|
|
62
|
+
namemode = 1
|
|
63
|
+
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
def __find_subjdl(self, body, index):
|
|
67
|
+
"""Find a full [] enclosure starting from index"""
|
|
68
|
+
result = ""
|
|
69
|
+
if body[index] != "[":
|
|
70
|
+
return (result, 0)
|
|
71
|
+
|
|
72
|
+
depth = 0
|
|
73
|
+
ind = index
|
|
74
|
+
while depth < 10:
|
|
75
|
+
ind1 = body.find("]", ind + 1)
|
|
76
|
+
ind2 = body.find("[", ind + 1)
|
|
77
|
+
if ind2 != -1 and ind2 < ind1:
|
|
78
|
+
depth += 1
|
|
79
|
+
ind = ind2
|
|
80
|
+
else:
|
|
81
|
+
if depth > 0:
|
|
82
|
+
depth -= 1
|
|
83
|
+
ind = ind1
|
|
84
|
+
else:
|
|
85
|
+
result = body[index : ind1 + 1]
|
|
86
|
+
if body[ind1 + 1] == ";":
|
|
87
|
+
return (result, ind1 + 2)
|
|
88
|
+
return result, 0
|
|
89
|
+
|
|
90
|
+
return result, 0
|
|
91
|
+
|
|
92
|
+
def insertAttributeInt(self, name, attribute):
|
|
93
|
+
"""Insert a named integer attribute"""
|
|
94
|
+
|
|
95
|
+
self.contents[name] = str(attribute)
|
|
96
|
+
|
|
97
|
+
def insertAttributeBool(self, name, attribute):
|
|
98
|
+
"""Insert a named boolean attribute"""
|
|
99
|
+
|
|
100
|
+
if attribute:
|
|
101
|
+
self.contents[name] = "true"
|
|
102
|
+
else:
|
|
103
|
+
self.contents[name] = "false"
|
|
104
|
+
|
|
105
|
+
def insertAttributeString(self, name, attribute):
|
|
106
|
+
"""Insert a named string attribute"""
|
|
107
|
+
|
|
108
|
+
self.contents[name] = '"' + str(attribute) + '"'
|
|
109
|
+
|
|
110
|
+
def insertAttributeVectorString(self, name, attributelist):
|
|
111
|
+
"""Insert a named string list attribute"""
|
|
112
|
+
|
|
113
|
+
tmp = ['"' + x + '"' for x in attributelist]
|
|
114
|
+
tmpstr = ",".join(tmp)
|
|
115
|
+
self.contents[name] = "{" + tmpstr + "}"
|
|
116
|
+
|
|
117
|
+
def insertAttributeVectorInt(self, name, attributelist):
|
|
118
|
+
"""Insert a named string list attribute"""
|
|
119
|
+
|
|
120
|
+
tmp = [str(x) for x in attributelist]
|
|
121
|
+
tmpstr = ",".join(tmp)
|
|
122
|
+
self.contents[name] = "{" + tmpstr + "}"
|
|
123
|
+
|
|
124
|
+
def insertAttributeVectorStringList(self, name, attributelist):
|
|
125
|
+
"""Insert a named list of string lists"""
|
|
126
|
+
|
|
127
|
+
listOfLists = []
|
|
128
|
+
for stringList in attributelist:
|
|
129
|
+
# tmp = map ( lambda x : '"' + x + '"', stringList )
|
|
130
|
+
tmpstr = ",".join(stringList)
|
|
131
|
+
listOfLists.append("{" + tmpstr + "}")
|
|
132
|
+
self.contents[name] = "{" + ",".join(listOfLists) + "}"
|
|
133
|
+
|
|
134
|
+
def lookupAttribute(self, name):
|
|
135
|
+
"""Check the presence of the given attribute"""
|
|
136
|
+
|
|
137
|
+
return name in self.contents
|
|
138
|
+
|
|
139
|
+
def set_expression(self, name, attribute):
|
|
140
|
+
"""Insert a named expression attribute"""
|
|
141
|
+
|
|
142
|
+
self.contents[name] = str(attribute)
|
|
143
|
+
|
|
144
|
+
def get_expression(self, name):
|
|
145
|
+
"""Get expression corresponding to a named attribute"""
|
|
146
|
+
|
|
147
|
+
if name in self.contents:
|
|
148
|
+
if isinstance(self.contents[name], int):
|
|
149
|
+
return str(self.contents[name])
|
|
150
|
+
return self.contents[name]
|
|
151
|
+
return ""
|
|
152
|
+
|
|
153
|
+
def isAttributeList(self, name):
|
|
154
|
+
"""Check if the given attribute is of the List type"""
|
|
155
|
+
attribute = self.get_expression(name).strip()
|
|
156
|
+
return attribute.startswith("{")
|
|
157
|
+
|
|
158
|
+
def getListFromExpression(self, name):
|
|
159
|
+
"""Get a list of strings from a given expression"""
|
|
160
|
+
|
|
161
|
+
tempString = self.get_expression(name).strip()
|
|
162
|
+
listMode = False
|
|
163
|
+
if tempString.startswith("{"):
|
|
164
|
+
tempString = tempString[1:-1]
|
|
165
|
+
listMode = True
|
|
166
|
+
|
|
167
|
+
tempString = tempString.replace(" ", "").replace("\n", "")
|
|
168
|
+
if tempString.find("{") < 0:
|
|
169
|
+
if not listMode:
|
|
170
|
+
tempString = tempString.replace('"', "")
|
|
171
|
+
if not tempString:
|
|
172
|
+
return []
|
|
173
|
+
return tempString.split(",")
|
|
174
|
+
|
|
175
|
+
resultList = []
|
|
176
|
+
while tempString:
|
|
177
|
+
if tempString.find("{") == 0:
|
|
178
|
+
end = tempString.find("}")
|
|
179
|
+
resultList.append(tempString[: end + 1])
|
|
180
|
+
tempString = tempString[end + 1 :]
|
|
181
|
+
if tempString.startswith(","):
|
|
182
|
+
tempString = tempString[1:]
|
|
183
|
+
elif tempString.find('"') == 0:
|
|
184
|
+
end = tempString[1:].find('"')
|
|
185
|
+
resultList.append(tempString[1 : end + 1])
|
|
186
|
+
tempString = tempString[end + 2 :]
|
|
187
|
+
if tempString.startswith(","):
|
|
188
|
+
tempString = tempString[1:]
|
|
189
|
+
else:
|
|
190
|
+
end = tempString.find(",")
|
|
191
|
+
if end < 0:
|
|
192
|
+
resultList.append(tempString.replace('"', "").replace(" ", ""))
|
|
193
|
+
break
|
|
194
|
+
else:
|
|
195
|
+
resultList.append(tempString[:end].replace('"', "").replace(" ", ""))
|
|
196
|
+
tempString = tempString[end + 1 :]
|
|
197
|
+
|
|
198
|
+
return resultList
|
|
199
|
+
|
|
200
|
+
def getDictionaryFromSubJDL(self, name):
|
|
201
|
+
"""Get a dictionary of the JDL attributes from a subsection"""
|
|
202
|
+
|
|
203
|
+
tempList = self.get_expression(name)[1:-1]
|
|
204
|
+
resDict = {}
|
|
205
|
+
for item in tempList.split(";"):
|
|
206
|
+
if len(item.split("=")) == 2:
|
|
207
|
+
resDict[item.split("=")[0].strip()] = item.split("=")[1].strip().replace('"', "")
|
|
208
|
+
else:
|
|
209
|
+
return {}
|
|
210
|
+
|
|
211
|
+
return resDict
|
|
212
|
+
|
|
213
|
+
def deleteAttribute(self, name):
|
|
214
|
+
"""Delete a named attribute"""
|
|
215
|
+
|
|
216
|
+
if name in self.contents:
|
|
217
|
+
del self.contents[name]
|
|
218
|
+
return 1
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
def isOK(self):
|
|
222
|
+
"""Check the JDL validity - to be defined"""
|
|
223
|
+
|
|
224
|
+
if self.contents:
|
|
225
|
+
return 1
|
|
226
|
+
return 0
|
|
227
|
+
|
|
228
|
+
def asJDL(self):
|
|
229
|
+
"""Convert the JDL description into a string"""
|
|
230
|
+
|
|
231
|
+
result = []
|
|
232
|
+
for name, value in sorted(self.contents.items()):
|
|
233
|
+
if value[0:1] == "{":
|
|
234
|
+
result += [4 * " " + name + " = \n"]
|
|
235
|
+
result += [8 * " " + "{\n"]
|
|
236
|
+
strings = value[1:-1].split(",")
|
|
237
|
+
for st in strings:
|
|
238
|
+
result += [12 * " " + st.strip() + ",\n"]
|
|
239
|
+
result[-1] = result[-1][:-2]
|
|
240
|
+
result += ["\n" + 8 * " " + "};\n"]
|
|
241
|
+
elif value[0:1] == "[":
|
|
242
|
+
tempad = ClassAd(value)
|
|
243
|
+
tempjdl = tempad.asJDL() + ";"
|
|
244
|
+
lines = tempjdl.split("\n")
|
|
245
|
+
result += [4 * " " + name + " = \n"]
|
|
246
|
+
for line in lines:
|
|
247
|
+
result += [8 * " " + line + "\n"]
|
|
248
|
+
|
|
249
|
+
else:
|
|
250
|
+
result += [4 * " " + name + " = " + str(value) + ";\n"]
|
|
251
|
+
if result:
|
|
252
|
+
result[-1] = result[-1][:-1]
|
|
253
|
+
return "[ \n" + "".join(result) + "\n]"
|
|
254
|
+
|
|
255
|
+
def getAttributeString(self, name):
|
|
256
|
+
"""Get String type attribute value"""
|
|
257
|
+
value = ""
|
|
258
|
+
if self.lookupAttribute(name):
|
|
259
|
+
value = self.get_expression(name).replace('"', "")
|
|
260
|
+
return value
|
|
261
|
+
|
|
262
|
+
def getAttributeInt(self, name):
|
|
263
|
+
"""Get Integer type attribute value"""
|
|
264
|
+
value = None
|
|
265
|
+
if self.lookupAttribute(name):
|
|
266
|
+
try:
|
|
267
|
+
value = int(self.get_expression(name).replace('"', ""))
|
|
268
|
+
except Exception:
|
|
269
|
+
value = None
|
|
270
|
+
return value
|
|
271
|
+
|
|
272
|
+
def getAttributeBool(self, name):
|
|
273
|
+
"""Get Boolean type attribute value"""
|
|
274
|
+
if not self.lookupAttribute(name):
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
value = self.get_expression(name).replace('"', "")
|
|
278
|
+
return value.lower() == "true"
|
|
279
|
+
|
|
280
|
+
def getAttributeFloat(self, name):
|
|
281
|
+
"""Get Float type attribute value"""
|
|
282
|
+
value = None
|
|
283
|
+
if self.lookupAttribute(name):
|
|
284
|
+
try:
|
|
285
|
+
value = float(self.get_expression(name).replace('"', ""))
|
|
286
|
+
except Exception:
|
|
287
|
+
value = None
|
|
288
|
+
return value
|
|
289
|
+
|
|
290
|
+
def getAttributes(self) -> list[str]:
|
|
291
|
+
"""Get the list of all the attribute names
|
|
292
|
+
|
|
293
|
+
:return: list of names as strings
|
|
294
|
+
"""
|
|
295
|
+
return list(self.contents)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ClassAd utilities for DIRACCommon"""
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Transformation classes around the JDL format."""
|
|
2
|
+
|
|
3
|
+
from diraccfg import CFG
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from DIRACCommon.Core.Utilities.ReturnValues import S_OK, S_ERROR
|
|
7
|
+
from DIRACCommon.Core.Utilities import List
|
|
8
|
+
from DIRACCommon.Core.Utilities.ClassAd.ClassAdLight import ClassAd
|
|
9
|
+
from DIRACCommon.WorkloadManagementSystem.Utilities.JobModel import BaseJobDescriptionModel
|
|
10
|
+
|
|
11
|
+
ARGUMENTS = "Arguments"
|
|
12
|
+
BANNED_SITES = "BannedSites"
|
|
13
|
+
CPU_TIME = "CPUTime"
|
|
14
|
+
EXECUTABLE = "Executable"
|
|
15
|
+
EXECUTION_ENVIRONMENT = "ExecutionEnvironment"
|
|
16
|
+
GRID_CE = "GridCE"
|
|
17
|
+
INPUT_DATA = "InputData"
|
|
18
|
+
INPUT_DATA_POLICY = "InputDataPolicy"
|
|
19
|
+
INPUT_SANDBOX = "InputSandbox"
|
|
20
|
+
JOB_CONFIG_ARGS = "JobConfigArgs"
|
|
21
|
+
JOB_TYPE = "JobType"
|
|
22
|
+
JOB_GROUP = "JobGroup"
|
|
23
|
+
LOG_LEVEL = "LogLevel"
|
|
24
|
+
NUMBER_OF_PROCESSORS = "NumberOfProcessors"
|
|
25
|
+
MAX_NUMBER_OF_PROCESSORS = "MaxNumberOfProcessors"
|
|
26
|
+
MIN_NUMBER_OF_PROCESSORS = "MinNumberOfProcessors"
|
|
27
|
+
OUTPUT_DATA = "OutputData"
|
|
28
|
+
OUTPUT_PATH = "OutputPath"
|
|
29
|
+
OUTPUT_SE = "OutputSE"
|
|
30
|
+
PLATFORM = "Platform"
|
|
31
|
+
PRIORITY = "Priority"
|
|
32
|
+
STD_ERROR = "StdError"
|
|
33
|
+
STD_OUTPUT = "StdOutput"
|
|
34
|
+
OUTPUT_SANDBOX = "OutputSandbox"
|
|
35
|
+
JOB_NAME = "JobName"
|
|
36
|
+
SITE = "Site"
|
|
37
|
+
TAGS = "Tags"
|
|
38
|
+
|
|
39
|
+
OWNER = "Owner"
|
|
40
|
+
OWNER_GROUP = "OwnerGroup"
|
|
41
|
+
VO = "VirtualOrganization"
|
|
42
|
+
|
|
43
|
+
CREDENTIALS_FIELDS = {OWNER, OWNER_GROUP, VO}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def loadJDLAsCFG(jdl):
|
|
47
|
+
"""
|
|
48
|
+
Load a JDL as CFG
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def cleanValue(value):
|
|
52
|
+
value = value.strip()
|
|
53
|
+
if value[0] == '"':
|
|
54
|
+
entries = []
|
|
55
|
+
iPos = 1
|
|
56
|
+
current = ""
|
|
57
|
+
state = "in"
|
|
58
|
+
while iPos < len(value):
|
|
59
|
+
if value[iPos] == '"':
|
|
60
|
+
if state == "in":
|
|
61
|
+
entries.append(current)
|
|
62
|
+
current = ""
|
|
63
|
+
state = "out"
|
|
64
|
+
elif state == "out":
|
|
65
|
+
current = current.strip()
|
|
66
|
+
if current not in (",",):
|
|
67
|
+
return S_ERROR("value seems a list but is not separated in commas")
|
|
68
|
+
current = ""
|
|
69
|
+
state = "in"
|
|
70
|
+
else:
|
|
71
|
+
current += value[iPos]
|
|
72
|
+
iPos += 1
|
|
73
|
+
if state == "in":
|
|
74
|
+
return S_ERROR('value is opened with " but is not closed')
|
|
75
|
+
return S_OK(", ".join(entries))
|
|
76
|
+
else:
|
|
77
|
+
return S_OK(value.replace('"', ""))
|
|
78
|
+
|
|
79
|
+
def assignValue(key, value, cfg):
|
|
80
|
+
key = key.strip()
|
|
81
|
+
if len(key) == 0:
|
|
82
|
+
return S_ERROR("Invalid key name")
|
|
83
|
+
value = value.strip()
|
|
84
|
+
if not value:
|
|
85
|
+
return S_ERROR(f"No value for key {key}")
|
|
86
|
+
if value[0] == "{":
|
|
87
|
+
if value[-1] != "}":
|
|
88
|
+
return S_ERROR("Value '%s' seems a list but does not end in '}'" % (value))
|
|
89
|
+
valList = List.fromChar(value[1:-1])
|
|
90
|
+
for i in range(len(valList)):
|
|
91
|
+
result = cleanValue(valList[i])
|
|
92
|
+
if not result["OK"]:
|
|
93
|
+
return S_ERROR(f"Var {key} : {result['Message']}")
|
|
94
|
+
valList[i] = result["Value"]
|
|
95
|
+
if valList[i] is None:
|
|
96
|
+
return S_ERROR(f"List value '{value}' seems invalid for item {i}")
|
|
97
|
+
value = ", ".join(valList)
|
|
98
|
+
else:
|
|
99
|
+
result = cleanValue(value)
|
|
100
|
+
if not result["OK"]:
|
|
101
|
+
return S_ERROR(f"Var {key} : {result['Message']}")
|
|
102
|
+
nV = result["Value"]
|
|
103
|
+
if nV is None:
|
|
104
|
+
return S_ERROR(f"Value '{value} seems invalid")
|
|
105
|
+
value = nV
|
|
106
|
+
cfg.setOption(key, value)
|
|
107
|
+
return S_OK()
|
|
108
|
+
|
|
109
|
+
if jdl[0] == "[":
|
|
110
|
+
iPos = 1
|
|
111
|
+
else:
|
|
112
|
+
iPos = 0
|
|
113
|
+
key = ""
|
|
114
|
+
value = ""
|
|
115
|
+
action = "key"
|
|
116
|
+
insideLiteral = False
|
|
117
|
+
cfg = CFG()
|
|
118
|
+
while iPos < len(jdl):
|
|
119
|
+
char = jdl[iPos]
|
|
120
|
+
if char == ";" and not insideLiteral:
|
|
121
|
+
if key.strip():
|
|
122
|
+
result = assignValue(key, value, cfg)
|
|
123
|
+
if not result["OK"]:
|
|
124
|
+
return result
|
|
125
|
+
key = ""
|
|
126
|
+
value = ""
|
|
127
|
+
action = "key"
|
|
128
|
+
elif char == "[" and not insideLiteral:
|
|
129
|
+
key = key.strip()
|
|
130
|
+
if not key:
|
|
131
|
+
return S_ERROR("Invalid key in JDL")
|
|
132
|
+
if value.strip():
|
|
133
|
+
return S_ERROR(f"Key {key} seems to have a value and open a sub JDL at the same time")
|
|
134
|
+
result = loadJDLAsCFG(jdl[iPos:])
|
|
135
|
+
if not result["OK"]:
|
|
136
|
+
return result
|
|
137
|
+
subCfg, subPos = result["Value"]
|
|
138
|
+
cfg.createNewSection(key, contents=subCfg)
|
|
139
|
+
key = ""
|
|
140
|
+
value = ""
|
|
141
|
+
action = "key"
|
|
142
|
+
insideLiteral = False
|
|
143
|
+
iPos += subPos
|
|
144
|
+
elif char == "=" and not insideLiteral:
|
|
145
|
+
if action == "key":
|
|
146
|
+
action = "value"
|
|
147
|
+
insideLiteral = False
|
|
148
|
+
else:
|
|
149
|
+
value += char
|
|
150
|
+
elif char == "]" and not insideLiteral:
|
|
151
|
+
key = key.strip()
|
|
152
|
+
if len(key) > 0:
|
|
153
|
+
result = assignValue(key, value, cfg)
|
|
154
|
+
if not result["OK"]:
|
|
155
|
+
return result
|
|
156
|
+
return S_OK((cfg, iPos))
|
|
157
|
+
else:
|
|
158
|
+
if action == "key":
|
|
159
|
+
key += char
|
|
160
|
+
else:
|
|
161
|
+
value += char
|
|
162
|
+
if char == '"':
|
|
163
|
+
insideLiteral = not insideLiteral
|
|
164
|
+
iPos += 1
|
|
165
|
+
|
|
166
|
+
return S_OK((cfg, iPos))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def dumpCFGAsJDL(cfg, level=1, tab=" "):
|
|
170
|
+
indent = tab * level
|
|
171
|
+
contents = [f"{tab * (level - 1)}["]
|
|
172
|
+
sections = cfg.listSections()
|
|
173
|
+
|
|
174
|
+
for key in cfg:
|
|
175
|
+
if key in sections:
|
|
176
|
+
contents.append(f"{indent}{key} =")
|
|
177
|
+
contents.append(f"{dumpCFGAsJDL(cfg[key], level + 1, tab)};")
|
|
178
|
+
else:
|
|
179
|
+
val = List.fromChar(cfg[key])
|
|
180
|
+
# Some attributes are never lists
|
|
181
|
+
if len(val) < 2 or key in [ARGUMENTS, EXECUTABLE, STD_OUTPUT, STD_ERROR]:
|
|
182
|
+
value = cfg[key]
|
|
183
|
+
try:
|
|
184
|
+
try_value = float(value)
|
|
185
|
+
contents.append(f"{tab * level}{key} = {value};")
|
|
186
|
+
except Exception:
|
|
187
|
+
contents.append(f'{tab * level}{key} = "{value}";')
|
|
188
|
+
else:
|
|
189
|
+
contents.append(f"{indent}{key} =")
|
|
190
|
+
contents.append("%s{" % indent)
|
|
191
|
+
for iPos in range(len(val)):
|
|
192
|
+
try:
|
|
193
|
+
value = float(val[iPos])
|
|
194
|
+
except Exception:
|
|
195
|
+
val[iPos] = f'"{val[iPos]}"'
|
|
196
|
+
contents.append(",\n".join([f"{tab * (level + 1)}{value}" for value in val]))
|
|
197
|
+
contents.append("%s};" % indent)
|
|
198
|
+
contents.append(f"{tab * (level - 1)}]")
|
|
199
|
+
return "\n".join(contents)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Collection of DIRAC useful list related modules.
|
|
2
|
+
By default on Error they return None.
|
|
3
|
+
"""
|
|
4
|
+
import random
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, TypeVar
|
|
7
|
+
from collections.abc import Iterable
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def uniqueElements(aList: list) -> list:
|
|
13
|
+
"""Utility to retrieve list of unique elements in a list (order is kept)."""
|
|
14
|
+
|
|
15
|
+
# Use dict.fromkeys instead of set ensure the order is preserved
|
|
16
|
+
return list(dict.fromkeys(aList))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def appendUnique(aList: list, anObject: Any):
|
|
20
|
+
"""Append to list if object does not exist.
|
|
21
|
+
|
|
22
|
+
:param aList: list of elements
|
|
23
|
+
:param anObject: object you want to append
|
|
24
|
+
"""
|
|
25
|
+
if anObject not in aList:
|
|
26
|
+
aList.append(anObject)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def fromChar(inputString: str, sepChar: str = ","):
|
|
30
|
+
"""Generates a list splitting a string by the required character(s)
|
|
31
|
+
resulting string items are stripped and empty items are removed.
|
|
32
|
+
|
|
33
|
+
:param inputString: list serialised to string
|
|
34
|
+
:param sepChar: separator
|
|
35
|
+
:return: list of strings or None if sepChar has a wrong type
|
|
36
|
+
"""
|
|
37
|
+
# to prevent getting an empty String as argument
|
|
38
|
+
if not (isinstance(inputString, str) and isinstance(sepChar, str) and sepChar):
|
|
39
|
+
return None
|
|
40
|
+
return [fieldString.strip() for fieldString in inputString.split(sepChar) if len(fieldString.strip()) > 0]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def randomize(aList: Iterable[T]) -> list[T]:
|
|
44
|
+
"""Return a randomly sorted list.
|
|
45
|
+
|
|
46
|
+
:param aList: list to permute
|
|
47
|
+
"""
|
|
48
|
+
tmpList = list(aList)
|
|
49
|
+
random.shuffle(tmpList)
|
|
50
|
+
return tmpList
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def pop(aList, popElement):
|
|
54
|
+
"""Pop the first element equal to popElement from the list.
|
|
55
|
+
|
|
56
|
+
:param aList: list
|
|
57
|
+
:type aList: python:list
|
|
58
|
+
:param popElement: element to pop
|
|
59
|
+
"""
|
|
60
|
+
if popElement in aList:
|
|
61
|
+
return aList.pop(aList.index(popElement))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def stringListToString(aList: list) -> str:
|
|
65
|
+
"""This function is used for making MySQL queries with a list of string elements.
|
|
66
|
+
|
|
67
|
+
:param aList: list to be serialized to string for making queries
|
|
68
|
+
"""
|
|
69
|
+
return ",".join(f"'{x}'" for x in aList)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def intListToString(aList: list) -> str:
|
|
73
|
+
"""This function is used for making MySQL queries with a list of int elements.
|
|
74
|
+
|
|
75
|
+
:param aList: list to be serialized to string for making queries
|
|
76
|
+
"""
|
|
77
|
+
return ",".join(str(x) for x in aList)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def getChunk(aList: list, chunkSize: int):
|
|
81
|
+
"""Generator yielding chunk from a list of a size chunkSize.
|
|
82
|
+
|
|
83
|
+
:param aList: list to be splitted
|
|
84
|
+
:param chunkSize: lenght of one chunk
|
|
85
|
+
:raise: StopIteration
|
|
86
|
+
|
|
87
|
+
Usage:
|
|
88
|
+
|
|
89
|
+
>>> for chunk in getChunk( aList, chunkSize=10):
|
|
90
|
+
process( chunk )
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
chunkSize = int(chunkSize)
|
|
94
|
+
for i in range(0, len(aList), chunkSize):
|
|
95
|
+
yield aList[i : i + chunkSize]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def breakListIntoChunks(aList: list, chunkSize: int):
|
|
99
|
+
"""This function takes a list as input and breaks it into list of size 'chunkSize'.
|
|
100
|
+
It returns a list of lists.
|
|
101
|
+
|
|
102
|
+
:param aList: list of elements
|
|
103
|
+
:param chunkSize: len of a single chunk
|
|
104
|
+
:return: list of lists of length of chunkSize
|
|
105
|
+
:raise: RuntimeError if numberOfFilesInChunk is less than 1
|
|
106
|
+
"""
|
|
107
|
+
if chunkSize < 1:
|
|
108
|
+
raise RuntimeError("chunkSize cannot be less than 1")
|
|
109
|
+
if isinstance(aList, (set, dict, tuple, {}.keys().__class__, {}.items().__class__, {}.values().__class__)):
|
|
110
|
+
aList = list(aList)
|
|
111
|
+
return [chunk for chunk in getChunk(aList, chunkSize)]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def getIndexInList(anItem: Any, aList: list) -> int:
|
|
115
|
+
"""Return the index of the element x in the list l
|
|
116
|
+
or sys.maxint if it does not exist
|
|
117
|
+
|
|
118
|
+
:param anItem: element to look for
|
|
119
|
+
:param aList: list to look into
|
|
120
|
+
|
|
121
|
+
:return: the index or sys.maxint
|
|
122
|
+
"""
|
|
123
|
+
# try:
|
|
124
|
+
if anItem in aList:
|
|
125
|
+
return aList.index(anItem)
|
|
126
|
+
else:
|
|
127
|
+
return sys.maxsize
|