Skip to content

Commit 3b4ca4c

Browse files
committed
query_builder has logic for subset and range criteria, tests pass for those additions
1 parent 4eb97ea commit 3b4ca4c

File tree

8 files changed

+161
-2771
lines changed

8 files changed

+161
-2771
lines changed

examples/addtags.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def main(args):
6767
query="FormattedID = %s" % story_id,
6868
server_ping=False, isolated_workspace=True, instance=True)
6969
#print(story.details())
70-
print "story tags after deleting the '%s' Tag" % (droppable_tags[0].Name)
70+
print("story tags after deleting the '%s' Tag" % (droppable_tags[0].Name))
7171

7272
story_tags = [str(tag.Name) for tag in story.Tags]
7373
print(story_tags)

examples/crtask.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def main(args):
2626
sys.exit(1)
2727
storyID = args[0]
2828

29-
server, user, password, apikey, workspace, project = rallyWorkset(options)
29+
server, username, password, apikey, workspace, project = rallyWorkset(options)
3030
if apikey:
3131
rally = Rally(server, apikey=apikey, workspace=workspace, project=project)
3232
else:

pyral/hydrate.py.bkp

Lines changed: 0 additions & 202 deletions
This file was deleted.

pyral/query_builder.py

Lines changed: 37 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class RallyQueryFormatter(object):
114114
CONJUNCTIONS = ['and', 'AND', 'or', 'OR']
115115
CONJUNCTION_PATT = re.compile(r'\s+(AND|OR)\s+', re.I | re.M)
116116
ATTR_IDENTIFIER = r'[\w\.]+[a-zA-Z0-9]' # gotta be word-like possibly separated by '.' chars
117-
RELATIONSHIP = r'=|!=|>|<|>=|<=|contains|!contains|in|!in|between|!between'
117+
RELATIONSHIP = r'=|!=|>|<|>=|<=|contains|!contains'
118118
ATTR_VALUE = r'"[^"]+"|[^ ]+' # double quoted value or has no leading, embedded or trailing spaces
119119
QUERY_CRITERIA_PATTERN = re.compile(r'^(%s) (%s) (%s)$' % (ATTR_IDENTIFIER, RELATIONSHIP, ATTR_VALUE), re.M)
120120

@@ -198,25 +198,25 @@ def _encode(condition):
198198
## print("RallyQueryFormatter parts: %s" % repr(parts))
199199
##
200200
# if no CONJUNCTION is in parts, use the condition as is (simple case)
201+
# OR if the criteria looks like subset query or a range query
201202
conjunctions = [p for p in parts if p in RallyQueryFormatter.CONJUNCTIONS]
202-
if not conjunctions:
203-
if mo := re.search(r'^(\w+)\s+(!?in)\s+(.+)$', criteria, flags=re.I):
203+
if not conjunctions or re.search(r'!?between .+\s+and\s+', criteria, flags=re.I):
204+
# Is this a subset expression, foo in baz,korn or foo !in burgers,fries,toast
205+
mo = re.search(r'^(\w+)\s+(!?in)\s+(.+)$', criteria, flags=re.I)
206+
if mo:
204207
attr_name, cond, values = mo.group(1), mo.group(2), mo.group(3)
205208
# Rally WSAPI supports attr_name in value1,value2,... directly but not so with !in
206209
if cond.lower() == '!in': # we must construct an OR'ed express with != for each listed value
207210
# Rally WSAPI supports attr_name in value1,value2,... directly but not so with !in
208-
criteria = RallyQueryFormatter.constructORedExpression(attr_name, cond, values)
209-
elif mo := re.search(r'^(\w+) (!?between)\s+(.+)\s+and\s+(.+)$', criteria, flags=re.I):
210-
attr_name, cond, lesser, greater = mo.group(1), mo.group(2), mo.group(3), mo.group(4)
211-
rlns = ['>=', '<='] if cond.lower() == 'between' else ['<', '>']
212-
rln_op = 'AND' if cond.lower() == 'between' else 'OR'
213-
lcond = '%s %s %s' % (attr_name, rlns[0], lesser)
214-
gcond = '%s %s %s' % (attr_name, rlns[1], greater)
215-
criteria = "(%s) %s (%s)" % (lcond, rln_op, gcond)
211+
criteria = RallyQueryFormatter.constructSubsetExpression(attr_name, cond, values)
212+
else:
213+
# Is this a range expression, someDate between today and nextYear
214+
mo = re.search(r'^(\w+) (!?between)\s+(.+)\s+and\s+(.+)$', criteria, flags=re.I)
215+
if mo:
216+
attr_name, cond, lesser, greater = mo.group(1), mo.group(2), mo.group(3), mo.group(4)
217+
criteria = RallyQueryFormatter.constructRangefulExpression(attr_name, cond, lesser, greater)
218+
216219
expression = quote(criteria.strip()).replace('%28', '(').replace('%29', ')')
217-
##
218-
## print("RallyQueryFormatter.no_conjunctions: |%s|" % expression)
219-
##
220220
return expression
221221

222222
parts = RallyQueryFormatter.validatePartsSyntax(parts)
@@ -241,42 +241,16 @@ def _encode(condition):
241241
@staticmethod
242242
def validatePartsSyntax(parts):
243243
attr_ident = r'[\w\.]+[a-zA-Z0-9]'
244-
relationship = r'=|!=|>|<|>=|<=|contains|!contains|in|!in|between|!between'
244+
relationship = r'=|!=|>|<|>=|<=|contains|!contains'
245245
attr_value = r'"[^"]+"|[^" ]+'
246246
criteria_pattern = re.compile(r'^(%s) (%s) (%s)$' % (attr_ident, relationship, attr_value))
247247
quoted_value_pattern = re.compile(r'^(%s) (%s) ("[^"]+")$' % (attr_ident, relationship))
248248
unquoted_value_pattern = re.compile(r'^(%s) (%s) ([^"].+[^"])$' % (attr_ident, relationship))
249-
subset_pattern = re.compile(r'(%s) (in|!in) (%s)' % (attr_ident, attr_value), flags=re.I)
250-
range_pattern = re.compile(r'(%s) (between|!between) (%s) and (%s)' % (attr_ident, attr_value, attr_value), flags=re.I)
251249

252250
valid_parts = []
253251
front = ""
254252
while parts:
255253
part = "%s%s" % (front, parts.pop(0))
256-
mo = subset_pattern.match(part)
257-
if mo:
258-
rln = '!=' if mo.group(1).startswith('!') else '='
259-
attr_name, values = mo.group(0), mo.group(2)
260-
# if rln == '=':
261-
# binary_expr = constructORedExpression(attr_name, rln, values)
262-
# else:
263-
# binary_expr = constructANDedExpression(attr_name, rln, values)
264-
valid_parts.append(binary_expr)
265-
continue
266-
mo = range_pattern.match(part)
267-
if mo:
268-
attr_name, cond = mo.group(0), mo.group(1)
269-
lesser_val, greater_val = mo.group(2), mo.group(3)
270-
rlns = ['<', '>'] if cond.startswith('!') else ['>=', '<=']
271-
# in range
272-
if cond.lower() == 'between':
273-
binary_expr = '((%s %s %s) AND (%s %s %s))' % \
274-
(attr_name, rlns[0], lesser_val, attr_name, rlns[1], greater_val)
275-
else:
276-
binary_expr = '((%s %s %s) OR (%s %s %s))' % \
277-
(attr_name, rlns[0], lesser_val, attr_name, rlns[1], greater_val)
278-
valid_parts.append(binary_expr)
279-
continue
280254
mo = criteria_pattern.match(part)
281255
if mo:
282256
valid_parts.append(part)
@@ -297,44 +271,49 @@ def validatePartsSyntax(parts):
297271

298272
return valid_parts
299273

274+
#
300275
# subset and range related ops for building queries
301-
276+
#
302277
@staticmethod
303-
def constructORedExpression(field, relation, values):
278+
def constructSubsetExpression(field, relation, values):
304279
"""
305280
intended for use when a subset operator (in or !in) is in play
306281
State in Defined, Accepted, Relased
307282
needs an ORed expression ((f = D OR f = A) OR ((f = R)))
308283
State !in Working, Fixed, Testing
309284
needs an ANDed expression ((f != W AND f != F) AND ((f != T)))
310285
"""
311-
operator = "="
312-
if relation == '!in':
286+
operator, conjunction = "=", 'OR'
287+
if relation.lower() == '!in':
313288
operator = "!="
289+
conjunction = 'AND'
290+
if isinstance(values, str):
291+
if values.count(',') == 0: # no commas equal only 1 value considered, put it in a list
292+
values = [values]
293+
else:
294+
values = [item.lstrip().rstrip() for item in values.split(',')]
314295
if len(values) == 1:
315-
return f'({field} {operator} "{values[0]})"'
316-
binary_expression = f'(({field} {operator} "{values[0]}") OR ({field} {operator} "{values[1]}"))'
296+
return f'{field} {operator} "{values[0]}"'
297+
val1, val2 = values[:2]
298+
binary_expression = f'({field} {operator} "{val1}") {conjunction} ({field} {operator} "{val2}")'
317299
for value in values[2:]:
318-
binary_expression = f'({binary_expression} OR ({field} {operator} "{value}"))'
300+
binary_expression = f'({binary_expression}) {conjunction} ({field} {operator} "{value}")'
319301
return binary_expression
320302

321303
@staticmethod
322-
def constructANDedExpression(field, relation, values):
304+
def constructRangefulExpression(attr_name, cond, lesser, greater):
323305
"""
324306
intended for use when a range operator (between or !between) is in play
325307
DevPhase between 2021-05-23 and 2021-07-09
326308
needs a single ANDed expression ((dp >= d1) AND (dp <= d1)))
327309
DevPhase !between 2021-12-19 and 2022-01-03
328310
needs a single ORed expression ((dp < d1) OR (dp > d1)))
329311
"""
330-
operator = "="
331-
if relation == '!in':
332-
operator = "!="
333-
if len(values) == 1:
334-
return f'({field} {operator} "{values[0]})"'
335-
binary_expression = f'(({field} {operator} "{values[0]}") AND ({field} {operator} "{values[1]}"))'
336-
for value in values[2:]:
337-
binary_expression = f'({binary_expression} AND ({field} {operator} "{value}"))'
338-
return binary_expression
312+
rlns = ['>=', '<='] if cond.lower() == 'between' else ['<', '>']
313+
conjunction= 'AND' if cond.lower() == 'between' else 'OR'
314+
lcond = '%s %s %s' % (attr_name, rlns[0], lesser)
315+
gcond = '%s %s %s' % (attr_name, rlns[1], greater)
316+
expression = "(%s) %s (%s)" % (lcond, conjunction, gcond)
317+
return expression
339318

340319
##################################################################################################

0 commit comments

Comments
 (0)