Skip to content

Commit 0a4f397

Browse files
authored
Merge pull request #8289 from chrisburr/feature/move-more-to-dirac-common
refactor: move stateless utilities to DIRACCommon
2 parents 34a4edf + 8e4479a commit 0a4f397

File tree

29 files changed

+872
-624
lines changed

29 files changed

+872
-624
lines changed

dirac-common/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ This package solves the circular dependency issue where DiracX needs DIRAC utili
88

99
## Contents
1010

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
1316

1417
## Installation
1518

@@ -20,7 +23,7 @@ pip install DIRACCommon
2023
## Usage
2124

2225
```python
23-
from DIRACCommon.Utils.ReturnValues import S_OK, S_ERROR
26+
from DIRACCommon.Core.Utilities.ReturnValues import S_OK, S_ERROR
2427

2528
def my_function():
2629
if success:
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""ClassAd utilities for DIRACCommon"""
File renamed without changes.

dirac-common/src/DIRACCommon/Utils/ReturnValues.py renamed to dirac-common/src/DIRACCommon/Core/Utilities/ReturnValues.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from typing import Any, Callable, cast, Generic, Literal, overload, Type, TypeVar, Union
1515
from typing_extensions import TypedDict, ParamSpec, NotRequired
1616

17-
from DIRACCommon.Utils.DErrno import strerror
17+
from DIRACCommon.Core.Utilities.DErrno import strerror
1818

1919

2020
T = TypeVar("T")
File renamed without changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""DIRACCommon Core utilities"""
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Stateless JobDB utilities extracted from DIRAC for DIRACCommon"""
2+
3+
from __future__ import annotations
4+
5+
import base64
6+
import zlib
7+
8+
9+
def compressJDL(jdl):
10+
"""Return compressed JDL string."""
11+
return base64.b64encode(zlib.compress(jdl.encode(), -1)).decode()
12+
13+
14+
def extractJDL(compressedJDL):
15+
"""Return decompressed JDL string."""
16+
# the starting bracket is guaranteeed by JobManager.submitJob
17+
# we need the check to be backward compatible
18+
if isinstance(compressedJDL, bytes):
19+
if compressedJDL.startswith(b"["):
20+
return compressedJDL.decode()
21+
else:
22+
if compressedJDL.startswith("["):
23+
return compressedJDL
24+
return zlib.decompress(base64.b64decode(compressedJDL)).decode()
25+
26+
27+
def fixJDL(jdl: str) -> str:
28+
"""Fix possible lack of brackets in JDL"""
29+
# 1.- insert original JDL on DB and get new JobID
30+
# Fix the possible lack of the brackets in the JDL
31+
if jdl.strip()[0].find("[") != 0:
32+
jdl = "[" + jdl + "]"
33+
return jdl
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""DIRACCommon WorkloadManagementSystem DB utilities"""

0 commit comments

Comments
 (0)