DIRAC 9.0.0a66__py3-none-any.whl → 9.0.0a68__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.
Files changed (38) hide show
  1. DIRAC/ConfigurationSystem/Client/Helpers/Resources.py +11 -43
  2. DIRAC/ConfigurationSystem/Client/Helpers/test/Test_Helpers.py +0 -16
  3. DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py +1 -1
  4. DIRAC/Core/Security/IAMService.py +4 -3
  5. DIRAC/Core/Utilities/ClassAd/ClassAdLight.py +4 -290
  6. DIRAC/Core/Utilities/DErrno.py +1 -1
  7. DIRAC/Core/Utilities/JDL.py +1 -195
  8. DIRAC/Core/Utilities/List.py +1 -127
  9. DIRAC/Core/Utilities/ReturnValues.py +2 -2
  10. DIRAC/Core/Utilities/StateMachine.py +12 -178
  11. DIRAC/Core/Utilities/TimeUtilities.py +10 -253
  12. DIRAC/Core/Utilities/test/Test_JDL.py +0 -3
  13. DIRAC/DataManagementSystem/DB/FTS3DB.py +3 -0
  14. DIRAC/RequestManagementSystem/DB/test/RMSTestScenari.py +2 -0
  15. DIRAC/Resources/Catalog/RucioFileCatalogClient.py +1 -1
  16. DIRAC/Resources/Computing/test/Test_PoolComputingElement.py +2 -1
  17. DIRAC/Workflow/Modules/test/Test_Modules.py +5 -0
  18. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobAgent.py +2 -0
  19. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_PushJobAgent.py +1 -0
  20. DIRAC/WorkloadManagementSystem/Client/JobState/JobManifest.py +32 -261
  21. DIRAC/WorkloadManagementSystem/Client/JobStatus.py +8 -93
  22. DIRAC/WorkloadManagementSystem/DB/JobDBUtils.py +18 -147
  23. DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +4 -2
  24. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +21 -5
  25. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapperTemplate.py +4 -0
  26. DIRAC/WorkloadManagementSystem/Utilities/JobModel.py +28 -199
  27. DIRAC/WorkloadManagementSystem/Utilities/JobStatusUtility.py +1 -63
  28. DIRAC/WorkloadManagementSystem/Utilities/ParametricJob.py +7 -171
  29. DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobModel.py +1 -5
  30. DIRAC/WorkloadManagementSystem/Utilities/test/Test_ParametricJob.py +45 -128
  31. {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/METADATA +2 -2
  32. {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/RECORD +36 -38
  33. DIRAC/Core/Utilities/test/Test_List.py +0 -150
  34. DIRAC/Core/Utilities/test/Test_Time.py +0 -88
  35. {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/WHEEL +0 -0
  36. {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/entry_points.txt +0 -0
  37. {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/licenses/LICENSE +0 -0
  38. {dirac-9.0.0a66.dist-info → dirac-9.0.0a68.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,14 @@
1
1
  """ Helper for the CS Resources section
2
2
  """
3
- import re
4
3
  from urllib import parse
5
4
 
6
5
  from DIRAC import S_ERROR, S_OK, gConfig, gLogger
7
6
  from DIRAC.ConfigurationSystem.Client.Helpers.Path import cfgPath
8
7
  from DIRAC.Core.Utilities.List import fromChar, uniqueElements
8
+ from DIRACCommon.ConfigurationSystem.Client.Helpers.Resources import (
9
+ getDIRACPlatform as _getDIRACPlatform,
10
+ _platformSortKey,
11
+ )
9
12
 
10
13
  gBaseResourcesSection = "/Resources"
11
14
 
@@ -328,8 +331,8 @@ def getCompatiblePlatforms(originalPlatforms):
328
331
  if not (result["OK"] and result["Value"]):
329
332
  return S_ERROR("OS compatibility info not found")
330
333
 
331
- platformsDict = {k: v.replace(" ", "").split(",") for k, v in result["Value"].items()} # can be an iterator
332
- for k, v in platformsDict.items(): # can be an iterator
334
+ platformsDict = {k: v.replace(" ", "").split(",") for k, v in result["Value"].items()}
335
+ for k, v in platformsDict.items():
333
336
  if k not in v:
334
337
  v.append(k)
335
338
 
@@ -355,7 +358,6 @@ def getDIRACPlatform(OSList):
355
358
  :param list OSList: list of platforms defined by resource providers
356
359
  :return: a list of DIRAC platforms that can be specified in job descriptions
357
360
  """
358
-
359
361
  # For backward compatibility allow a single string argument
360
362
  osList = OSList
361
363
  if isinstance(OSList, str):
@@ -365,31 +367,12 @@ def getDIRACPlatform(OSList):
365
367
  if not (result["OK"] and result["Value"]):
366
368
  return S_ERROR("OS compatibility info not found")
367
369
 
368
- platformsDict = {k: v.replace(" ", "").split(",") for k, v in result["Value"].items()} # can be an iterator
369
- for k, v in platformsDict.items(): # can be an iterator
370
+ platformsDict = {k: set(v.replace(" ", "").split(",")) for k, v in result["Value"].items()}
371
+ for k, v in platformsDict.items():
370
372
  if k not in v:
371
- v.append(k)
372
-
373
- # making an OS -> platforms dict
374
- os2PlatformDict = dict()
375
- for platform, osItems in platformsDict.items(): # can be an iterator
376
- for osItem in osItems:
377
- if os2PlatformDict.get(osItem):
378
- os2PlatformDict[osItem].append(platform)
379
- else:
380
- os2PlatformDict[osItem] = [platform]
381
-
382
- platforms = []
383
- for os in osList:
384
- if os in os2PlatformDict:
385
- platforms += os2PlatformDict[os]
386
-
387
- if not platforms:
388
- return S_ERROR(f"No compatible DIRAC platform found for {','.join(OSList)}")
373
+ v.add(k)
389
374
 
390
- platforms.sort(key=_platformSortKey, reverse=True)
391
-
392
- return S_OK(platforms)
375
+ return _getDIRACPlatform(osList, platformsDict)
393
376
 
394
377
 
395
378
  def getDIRACPlatforms():
@@ -451,7 +434,7 @@ def getInfoAboutProviders(of=None, providerName=None, option="", section=""):
451
434
  result = gConfig.getConfigurationTree(relPath)
452
435
  if not result["OK"]:
453
436
  return result
454
- for key, value in result["Value"].items(): # can be an iterator
437
+ for key, value in result["Value"].items():
455
438
  if value:
456
439
  resDict[key.replace(relPath, "")] = value
457
440
  return S_OK(resDict)
@@ -459,18 +442,3 @@ def getInfoAboutProviders(of=None, providerName=None, option="", section=""):
459
442
  return gConfig.getSections(f"{gBaseResourcesSection}/{of}Providers/{providerName}/{section}/")
460
443
  else:
461
444
  return S_OK(gConfig.getValue(f"{gBaseResourcesSection}/{of}Providers/{providerName}/{section}/{option}"))
462
-
463
-
464
- def _platformSortKey(version: str) -> list[str]:
465
- # Loosely based on distutils.version.LooseVersion
466
- parts = []
467
- for part in re.split(r"(\d+|[a-z]+|\.| -)", version.lower()):
468
- if not part or part == ".":
469
- continue
470
- if part[:1] in "0123456789":
471
- part = part.zfill(8)
472
- else:
473
- while parts and parts[-1] == "00000000":
474
- parts.pop()
475
- parts.append(part)
476
- return parts
@@ -9,7 +9,6 @@ from DIRAC.ConfigurationSystem.Client import ConfigurationData
9
9
  from DIRAC.ConfigurationSystem.Client.Helpers.Resources import (
10
10
  getDIRACPlatform,
11
11
  getCompatiblePlatforms,
12
- _platformSortKey,
13
12
  getQueue,
14
13
  )
15
14
 
@@ -77,21 +76,6 @@ def test_getDIRACPlatform(mocker, mockGCReplyInput, requested, expectedRes, expe
77
76
  assert set(res["Value"]) == set(expectedValue), res["Value"]
78
77
 
79
78
 
80
- @pytest.mark.parametrize(
81
- "string,expected",
82
- [
83
- ("Darwin_arm64_12.4", ["darwin", "_", "arm", "64", "_", "12", "4"]),
84
- ("Linux_x86_64_glibc-2.17", ["linux", "_", "x", "86", "_", "64", "_", "glibc", "-", "2", "17"]),
85
- ("Linux_aarch64_glibc-2.28", ["linux", "_", "aarch", "64", "_", "glibc", "-", "2", "28"]),
86
- ],
87
- )
88
- def test_platformSortKey(string, expected):
89
- actual = _platformSortKey(string)
90
- for a, e in zip_longest(actual, expected):
91
- # Numbers are padded with zeros so string comparison works
92
- assert a.lstrip("0") == e
93
-
94
-
95
79
  @pytest.mark.parametrize(
96
80
  "mockGCReplyInput, requested, expectedRes, expectedValue",
97
81
  [
@@ -594,7 +594,7 @@ class VOMS2CSSynchronizer:
594
594
 
595
595
  # Try to fill in the DiracX section
596
596
  if self.useIAM:
597
- iam_subs = self.iamSrv.getUsersSub()
597
+ iam_subs = self.iamSrv.getUsersSub(self.vo)
598
598
  diracx_vo_config = {"DiracX": {"CsSync": {"VOs": {self.vo: {"UserSubjects": iam_subs}}}}}
599
599
  iam_sub_cfg = CFG()
600
600
  iam_sub_cfg.loadFromDict(diracx_vo_config)
@@ -144,7 +144,7 @@ class IAMService:
144
144
  result = S_OK({"Users": users, "Errors": errors})
145
145
  return result
146
146
 
147
- def getUsersSub(self) -> dict[str, str]:
147
+ def getUsersSub(self, vo=None) -> dict[str, str]:
148
148
  """
149
149
  Return the mapping based on IAM sub:
150
150
  {nickname : sub}
@@ -152,6 +152,7 @@ class IAMService:
152
152
  iam_users_raw = self._getIamUserDump()
153
153
  diracx_user_section = {}
154
154
  for user_info in iam_users_raw:
155
+ userGroups = [grp["display"] for grp in user_info.get("groups", [])]
155
156
  # The nickname is available in the list of attributes
156
157
  # (if configured so)
157
158
  # in the form {'name': 'nickname', 'value': 'chaen'}
@@ -165,8 +166,8 @@ class IAMService:
165
166
  except (KeyError, IndexError):
166
167
  nickname = user_info["userName"]
167
168
  sub = user_info["id"]
168
-
169
- diracx_user_section[nickname] = sub
169
+ if not vo or vo in userGroups:
170
+ diracx_user_section[nickname] = sub
170
171
  # reorder it
171
172
  diracx_user_section = dict(sorted(diracx_user_section.items()))
172
173
 
@@ -2,294 +2,8 @@
2
2
  Condor ClassAd library.
3
3
  """
4
4
 
5
+ # Import from DIRACCommon for backward compatibility
6
+ from DIRACCommon.Core.Utilities.ClassAd.ClassAdLight import ClassAd
5
7
 
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)
8
+ # Re-export for backward compatibility
9
+ __all__ = ["ClassAd"]
@@ -41,7 +41,7 @@ import importlib
41
41
  import sys
42
42
 
43
43
  # Import all the stateless parts from DIRACCommon
44
- from DIRACCommon.Utils.DErrno import * # noqa: F401, F403
44
+ from DIRACCommon.Core.Utilities.DErrno import * # noqa: F401, F403
45
45
 
46
46
  from DIRAC.Core.Utilities.Extensions import extensionsByPriority
47
47
 
@@ -1,203 +1,9 @@
1
1
  """Transformation classes around the JDL format."""
2
2
 
3
- from diraccfg import CFG
4
- from pydantic import ValidationError
3
+ from DIRACCommon.Core.Utilities.JDL import * # noqa: F403,F401
5
4
 
6
- from DIRAC import S_OK, S_ERROR
7
- from DIRAC.Core.Utilities import List
8
- from DIRAC.Core.Utilities.ClassAd.ClassAdLight import ClassAd
9
5
  from DIRAC.WorkloadManagementSystem.Utilities.JobModel import BaseJobDescriptionModel
10
6
 
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)
200
-
201
7
 
202
8
  def jdlToBaseJobDescriptionModel(classAd: ClassAd):
203
9
  """