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.
Files changed (46) hide show
  1. {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/PKG-INFO +9 -4
  2. {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/README.md +6 -3
  3. {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/pyproject.toml +2 -0
  4. diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/ClassAd/ClassAdLight.py +295 -0
  5. diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/ClassAd/__init__.py +1 -0
  6. diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/JDL.py +199 -0
  7. diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/List.py +127 -0
  8. {diraccommon-9.0.0a66/src/DIRACCommon/Utils → diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities}/ReturnValues.py +1 -1
  9. diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/StateMachine.py +185 -0
  10. diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities/TimeUtilities.py +259 -0
  11. diraccommon-9.0.0a67/src/DIRACCommon/Core/__init__.py +1 -0
  12. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Client/JobState/JobManifest.py +235 -0
  13. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Client/JobState/__init__.py +0 -0
  14. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Client/JobStatus.py +95 -0
  15. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Client/__init__.py +1 -0
  16. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/DB/JobDBUtils.py +170 -0
  17. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/DB/__init__.py +1 -0
  18. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Utilities/JobModel.py +236 -0
  19. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Utilities/JobStatusUtility.py +93 -0
  20. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Utilities/ParametricJob.py +179 -0
  21. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/Utilities/__init__.py +1 -0
  22. diraccommon-9.0.0a67/src/DIRACCommon/WorkloadManagementSystem/__init__.py +1 -0
  23. diraccommon-9.0.0a67/tests/Core/Utilities/ClassAd/__init__.py +0 -0
  24. diraccommon-9.0.0a67/tests/Core/Utilities/ClassAd/test_ClassAdLight.py +69 -0
  25. diraccommon-9.0.0a67/tests/Core/Utilities/__init__.py +0 -0
  26. {diraccommon-9.0.0a66/tests/Utils → diraccommon-9.0.0a67/tests/Core/Utilities}/test_DErrno.py +1 -1
  27. diraccommon-9.0.0a67/tests/Core/Utilities/test_JDL.py +113 -0
  28. diraccommon-9.0.0a67/tests/Core/Utilities/test_List.py +150 -0
  29. {diraccommon-9.0.0a66/tests/Utils → diraccommon-9.0.0a67/tests/Core/Utilities}/test_ReturnValues.py +7 -1
  30. diraccommon-9.0.0a67/tests/Core/Utilities/test_TimeUtilities.py +87 -0
  31. diraccommon-9.0.0a67/tests/Core/__init__.py +0 -0
  32. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Client/JobState/__init__.py +1 -0
  33. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Client/JobState/test_JobManifest.py +129 -0
  34. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Client/__init__.py +1 -0
  35. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/DB/__init__.py +0 -0
  36. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/DB/test_JobDBUtils.py +73 -0
  37. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Utilities/__init__.py +0 -0
  38. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Utilities/test_JobModel.py +264 -0
  39. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Utilities/test_JobStatusUtility.py +164 -0
  40. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/Utilities/test_ParametricJob.py +141 -0
  41. diraccommon-9.0.0a67/tests/WorkloadManagementSystem/__init__.py +0 -0
  42. {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/.gitignore +0 -0
  43. {diraccommon-9.0.0a66/src/DIRACCommon/Utils → diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities}/DErrno.py +0 -0
  44. {diraccommon-9.0.0a66/src/DIRACCommon/Utils → diraccommon-9.0.0a67/src/DIRACCommon/Core/Utilities}/__init__.py +0 -0
  45. {diraccommon-9.0.0a66 → diraccommon-9.0.0a67}/src/DIRACCommon/__init__.py +0 -0
  46. {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.0a66
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.Utils.ReturnValues`: DIRAC's S_OK/S_ERROR return value system
34
- - `DIRACCommon.Utils.DErrno`: DIRAC error codes and utilities
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.Utils.ReturnValues import S_OK, S_ERROR
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.Utils.ReturnValues`: DIRAC's S_OK/S_ERROR return value system
12
- - `DIRACCommon.Utils.DErrno`: DIRAC error codes and utilities
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.Utils.ReturnValues import S_OK, S_ERROR
26
+ from DIRACCommon.Core.Utilities.ReturnValues import S_OK, S_ERROR
24
27
 
25
28
  def my_function():
26
29
  if success:
@@ -21,6 +21,8 @@ classifiers = [
21
21
  ]
22
22
  dependencies = [
23
23
  "typing-extensions>=4.0.0",
24
+ "diraccfg",
25
+ "pydantic>=2.0.0",
24
26
  ]
25
27
  dynamic = ["version"]
26
28
 
@@ -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