devopsdriver 0.1.51__tar.gz → 0.1.52__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.
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/PKG-INFO +3 -3
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/README.md +1 -1
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/__init__.py +1 -1
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/azureobject.py +2 -2
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/workitem/client.py +10 -10
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/workitem/wiql.py +20 -7
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/sendmail.py +12 -4
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/settings.py +13 -11
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver.egg-info/PKG-INFO +3 -3
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver.egg-info/requires.txt +1 -1
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/pyproject.toml +1 -1
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_workitem_wiql.py +4 -4
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/LICENSE +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/__init__.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/builds/__init__.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/builds/build.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/builds/client.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/clients.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/pipeline/__init__.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/pipeline/client.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/pipeline/log.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/pipeline/pipeline.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/pipeline/run.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/timestamp.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/azdo/workitem/__init__.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/dataobject.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/github/__init__.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/github/client.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/manage_settings.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/template.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver/templates/manage_settings.txt.mako +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver.egg-info/SOURCES.txt +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver.egg-info/dependency_links.txt +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver.egg-info/entry_points.txt +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/devopsdriver.egg-info/top_level.txt +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/setup.cfg +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_azureobject.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_build.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_build_client.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_clients.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_pipeline.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_pipeline_client.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_pipeline_run.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_timestamp.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_azure_workitem_client.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_dataobject.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_manage_settings.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_sendmail.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_settings.py +0 -0
- {devopsdriver-0.1.51 → devopsdriver-0.1.52}/tests/test_template.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devopsdriver
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.52
|
|
4
4
|
Summary: DevOps tools
|
|
5
5
|
Author-email: Marc Page <marcallenpage@gmail.com>
|
|
6
6
|
License: This is free and unencumbered software released into the public domain.
|
|
@@ -51,7 +51,7 @@ Requires-Dist: keyring==25.6.0
|
|
|
51
51
|
Requires-Dist: setuptools==80.9.0
|
|
52
52
|
Requires-Dist: azure-devops==7.1.0b4
|
|
53
53
|
Requires-Dist: Mako==1.3.10
|
|
54
|
-
Requires-Dist: PyGithub==2.
|
|
54
|
+
Requires-Dist: PyGithub==2.8.1
|
|
55
55
|
Provides-Extra: dev
|
|
56
56
|
Requires-Dist: black>=24.3.0; extra == "dev"
|
|
57
57
|
Requires-Dist: pylint>=3.1.0; extra == "dev"
|
|
@@ -64,7 +64,7 @@ Dynamic: license-file
|
|
|
64
64
|
# devops-driver
|
|
65
65
|
|
|
66
66
|

|
|
67
|
-
[](https://pypi.org/project/devopsdriver/0.1.52/)
|
|
68
68
|
[](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
|
|
69
69
|
[](https://github.com/marcpage/devops-driver/graphs/contributors)
|
|
70
70
|
[](http://makeapullrequest.com)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# devops-driver
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-
[](https://pypi.org/project/devopsdriver/0.1.52/)
|
|
5
5
|
[](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
|
|
6
6
|
[](https://github.com/marcpage/devops-driver/graphs/contributors)
|
|
7
7
|
[](http://makeapullrequest.com)
|
|
@@ -17,13 +17,13 @@ class AzureObject(DataObject): # pylint: disable=too-few-public-methods
|
|
|
17
17
|
self.raw = azure_object
|
|
18
18
|
super().__init__(self.raw.as_dict())
|
|
19
19
|
|
|
20
|
-
def _parse_value(self, data
|
|
20
|
+
def _parse_value(self, data):
|
|
21
21
|
if isinstance(data, str) and Timestamp.is_timestamp(data):
|
|
22
22
|
return Timestamp(data)
|
|
23
23
|
|
|
24
24
|
return super()._parse_value(data)
|
|
25
25
|
|
|
26
|
-
def _get_field(self, name: str, data: dict)
|
|
26
|
+
def _get_field(self, name: str, data: dict):
|
|
27
27
|
value = super()._get_field(name, data)
|
|
28
28
|
|
|
29
29
|
if value is None and "fields" in data:
|
|
@@ -21,9 +21,9 @@ class Client:
|
|
|
21
21
|
def query(
|
|
22
22
|
self,
|
|
23
23
|
wiql: Wiql | str,
|
|
24
|
-
team_context: TeamContext = None,
|
|
25
|
-
time_precision: bool = None,
|
|
26
|
-
top: int = None,
|
|
24
|
+
team_context: TeamContext | None = None,
|
|
25
|
+
time_precision: bool | None = None,
|
|
26
|
+
top: int | None = None,
|
|
27
27
|
) -> WorkItemQueryResult:
|
|
28
28
|
"""Perform a wiql query
|
|
29
29
|
|
|
@@ -46,15 +46,15 @@ class Client:
|
|
|
46
46
|
def get_history( # pylint: disable=too-many-positional-arguments,too-many-arguments
|
|
47
47
|
self,
|
|
48
48
|
wi_id: int,
|
|
49
|
-
project: str = None,
|
|
50
|
-
top: int = None,
|
|
51
|
-
skip: int = None,
|
|
52
|
-
expand: str = None,
|
|
49
|
+
project: str | None = None,
|
|
50
|
+
top: int | None = None,
|
|
51
|
+
skip: int | None = None,
|
|
52
|
+
expand: str | None = None,
|
|
53
53
|
) -> list[AzureWorkItem]:
|
|
54
54
|
"""Simple wrapper around get_revisions"""
|
|
55
55
|
return self.client.get_revisions(wi_id, project, top, skip, expand)
|
|
56
56
|
|
|
57
|
-
def find_ids(self, wiql: Wiql | str, top: int = None) -> list[int]:
|
|
57
|
+
def find_ids(self, wiql: Wiql | str, top: int | None = None) -> list[int]:
|
|
58
58
|
"""Given a query, find the work item ids
|
|
59
59
|
|
|
60
60
|
Args:
|
|
@@ -73,9 +73,9 @@ class Client:
|
|
|
73
73
|
# query_results_type: workItem
|
|
74
74
|
# query_type: flat
|
|
75
75
|
# columns: list of name, reference_name, url
|
|
76
|
-
return [i.id for i in found.work_items]
|
|
76
|
+
return [i.id for i in found.work_items] if found.work_items else []
|
|
77
77
|
|
|
78
|
-
def find(self, wiql: Wiql | str, top: int = None) -> list[list[AzureObject]]:
|
|
78
|
+
def find(self, wiql: Wiql | str, top: int | None = None) -> list[list[AzureObject]]:
|
|
79
79
|
"""Gets the full history of items found in a WIQL search
|
|
80
80
|
|
|
81
81
|
Args:
|
|
@@ -151,7 +151,7 @@ class In(Compare): # pylint: disable=too-few-public-methods
|
|
|
151
151
|
def __init__(
|
|
152
152
|
self,
|
|
153
153
|
field: Field | str,
|
|
154
|
-
*values:
|
|
154
|
+
*values: Value | str | date | datetime | int | float,
|
|
155
155
|
):
|
|
156
156
|
super().__init__(
|
|
157
157
|
field,
|
|
@@ -167,7 +167,7 @@ class NotIn(Compare): # pylint: disable=too-few-public-methods
|
|
|
167
167
|
def __init__(
|
|
168
168
|
self,
|
|
169
169
|
field: Field | str,
|
|
170
|
-
*values:
|
|
170
|
+
*values: Value | str | date | datetime | int | float,
|
|
171
171
|
):
|
|
172
172
|
super().__init__(
|
|
173
173
|
field,
|
|
@@ -234,7 +234,7 @@ class GreaterThanOrEqual(Compare): # pylint: disable=too-few-public-methods
|
|
|
234
234
|
class Expression: # pylint: disable=too-few-public-methods
|
|
235
235
|
"""Join several compares"""
|
|
236
236
|
|
|
237
|
-
def __init__(self, operator: str, *compares
|
|
237
|
+
def __init__(self, operator: str, *compares):
|
|
238
238
|
self.operator = operator
|
|
239
239
|
self.expressions = compares
|
|
240
240
|
|
|
@@ -245,14 +245,14 @@ class Expression: # pylint: disable=too-few-public-methods
|
|
|
245
245
|
class And(Expression): # pylint: disable=too-few-public-methods
|
|
246
246
|
"""Join compares via AND"""
|
|
247
247
|
|
|
248
|
-
def __init__(self, *compares:
|
|
248
|
+
def __init__(self, *compares: Compare | Expression):
|
|
249
249
|
super().__init__("AND", *compares)
|
|
250
250
|
|
|
251
251
|
|
|
252
252
|
class Or(Expression): # pylint: disable=too-few-public-methods
|
|
253
253
|
"""join compares via OR"""
|
|
254
254
|
|
|
255
|
-
def __init__(self, *compares:
|
|
255
|
+
def __init__(self, *compares: Compare | Expression):
|
|
256
256
|
super().__init__("OR", *compares)
|
|
257
257
|
|
|
258
258
|
|
|
@@ -264,8 +264,10 @@ class Wiql:
|
|
|
264
264
|
self.search = None
|
|
265
265
|
self.order = []
|
|
266
266
|
self.snapshot = None
|
|
267
|
+
self.source = "WorkItems"
|
|
268
|
+
self.mode_type = None
|
|
267
269
|
|
|
268
|
-
def select(self, *fields:
|
|
270
|
+
def select(self, *fields: Field | str):
|
|
269
271
|
"""The fields to select
|
|
270
272
|
|
|
271
273
|
Returns:
|
|
@@ -286,6 +288,16 @@ class Wiql:
|
|
|
286
288
|
self.search = expression
|
|
287
289
|
return self
|
|
288
290
|
|
|
291
|
+
def from_source(self, source: str):
|
|
292
|
+
"""Sets the FROM field"""
|
|
293
|
+
self.source = source
|
|
294
|
+
return self
|
|
295
|
+
|
|
296
|
+
def mode(self, results_mode: str):
|
|
297
|
+
"""Sets the mode for link queries"""
|
|
298
|
+
self.mode_type = results_mode
|
|
299
|
+
return self
|
|
300
|
+
|
|
289
301
|
def order_by(self, *orders):
|
|
290
302
|
"""Set the fields to order the results by
|
|
291
303
|
|
|
@@ -315,4 +327,5 @@ class Wiql:
|
|
|
315
327
|
f" ORDER BY {', '.join(str(o) for o in self.order)}" if self.order else ""
|
|
316
328
|
)
|
|
317
329
|
asof = f" ASOF {str(self.snapshot)}" if self.snapshot else ""
|
|
318
|
-
|
|
330
|
+
mode = f" MODE({self.mode_type})" if self.mode_type else ""
|
|
331
|
+
return f"SELECT {select} FROM {self.source}{where}{order}{asof}{mode}"
|
|
@@ -41,11 +41,11 @@ def image_extension(data: bytes) -> str:
|
|
|
41
41
|
raise AttributeError("Image not a known format: " + ",".join(IMAGE_HEADERS))
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def send_email(
|
|
44
|
+
def send_email( # pylint: disable=too-many-locals
|
|
45
45
|
recipients: str | list[str],
|
|
46
46
|
subject: str,
|
|
47
47
|
html_body: str,
|
|
48
|
-
settings: Settings = None,
|
|
48
|
+
settings: Settings | None = None,
|
|
49
49
|
**image_data,
|
|
50
50
|
):
|
|
51
51
|
"""Sends an email with embedded images
|
|
@@ -65,7 +65,9 @@ def send_email(
|
|
|
65
65
|
", ".join(missing) + " not found in:\n" + "\n".join(settings.search_files)
|
|
66
66
|
)
|
|
67
67
|
sender = settings["smtp.sender"]
|
|
68
|
+
assert isinstance(sender, str), sender
|
|
68
69
|
username = settings.get("smtp.username", sender)
|
|
70
|
+
assert isinstance(username, str), username
|
|
69
71
|
message = MIMEMULTIPART()
|
|
70
72
|
message["Subject"] = subject
|
|
71
73
|
message["From"] = sender
|
|
@@ -85,8 +87,14 @@ def send_email(
|
|
|
85
87
|
)
|
|
86
88
|
message.attach(image)
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
server = settings["smtp.server"]
|
|
91
|
+
assert isinstance(server, str), server
|
|
92
|
+
port = settings["smtp.port"]
|
|
93
|
+
assert isinstance(port, int), port
|
|
94
|
+
password = settings["smtp.password"]
|
|
95
|
+
assert isinstance(password, str), password
|
|
96
|
+
with connection_type(server, port) as smtp:
|
|
89
97
|
smtp.set_debuglevel(False)
|
|
90
|
-
smtp.login(username,
|
|
98
|
+
smtp.login(username, password)
|
|
91
99
|
smtp.sendmail(sender, recipients, message.as_string())
|
|
92
100
|
smtp.quit()
|
|
@@ -147,7 +147,9 @@ class Settings:
|
|
|
147
147
|
}
|
|
148
148
|
ENV_VAR_PATTERN = regex(r"\${(\S+)}")
|
|
149
149
|
|
|
150
|
-
def __init__(
|
|
150
|
+
def __init__(
|
|
151
|
+
self, file: str, *directories, shared_name: str | None = None, **settings
|
|
152
|
+
):
|
|
151
153
|
"""Create a settings object using a file, directories to search, and settings overrides
|
|
152
154
|
|
|
153
155
|
Args:
|
|
@@ -172,7 +174,7 @@ class Settings:
|
|
|
172
174
|
self.environ = {}
|
|
173
175
|
self.secrets = {}
|
|
174
176
|
|
|
175
|
-
def __bypass(self, key: str, name: str, store: dict):
|
|
177
|
+
def __bypass(self, key: str, name: str | None, store: dict):
|
|
176
178
|
if name is None:
|
|
177
179
|
for setting_key, store_name in self.settings.get(key, {}).items():
|
|
178
180
|
store[setting_key] = store_name
|
|
@@ -181,7 +183,7 @@ class Settings:
|
|
|
181
183
|
store[key] = name
|
|
182
184
|
return self
|
|
183
185
|
|
|
184
|
-
def key(self, key: str, name: str = None):
|
|
186
|
+
def key(self, key: str, name: str | None = None):
|
|
185
187
|
"""Sets a keychain name to map to a settings value.
|
|
186
188
|
|
|
187
189
|
Args:
|
|
@@ -195,7 +197,7 @@ class Settings:
|
|
|
195
197
|
"""
|
|
196
198
|
return self.__bypass(key, name, self.secrets)
|
|
197
199
|
|
|
198
|
-
def cli(self, key: str, name: str = None):
|
|
200
|
+
def cli(self, key: str, name: str | None = None):
|
|
199
201
|
"""Sets a command line switch to map to a settings value.
|
|
200
202
|
|
|
201
203
|
Args:
|
|
@@ -209,7 +211,7 @@ class Settings:
|
|
|
209
211
|
"""
|
|
210
212
|
return self.__bypass(key, name, self.opts)
|
|
211
213
|
|
|
212
|
-
def env(self, key: str, name: str = None):
|
|
214
|
+
def env(self, key: str, name: str | None = None):
|
|
213
215
|
"""Sets an environment variable to map to a settings value.
|
|
214
216
|
|
|
215
217
|
Args:
|
|
@@ -232,7 +234,7 @@ class Settings:
|
|
|
232
234
|
return "${" + key + "}"
|
|
233
235
|
|
|
234
236
|
@staticmethod
|
|
235
|
-
def __patch(value
|
|
237
|
+
def __patch(value):
|
|
236
238
|
if isinstance(value, str):
|
|
237
239
|
return Settings.ENV_VAR_PATTERN.sub(
|
|
238
240
|
lambda m: Settings.__patch_instance(m.group(1)), value
|
|
@@ -257,7 +259,7 @@ class Settings:
|
|
|
257
259
|
secret_name = parts[1] if len(parts) == 2 else parts[0]
|
|
258
260
|
return (service, secret_name)
|
|
259
261
|
|
|
260
|
-
def __lookup(self, key: str, check: bool, default
|
|
262
|
+
def __lookup(self, key: str, check: bool, default=None):
|
|
261
263
|
# Settings passed in override everything
|
|
262
264
|
if key in self.overrides:
|
|
263
265
|
return True if check else self.overrides[key]
|
|
@@ -293,7 +295,7 @@ class Settings:
|
|
|
293
295
|
|
|
294
296
|
return Settings.__patch(level.get(keys[-1], default))
|
|
295
297
|
|
|
296
|
-
def get(self, key: str, default
|
|
298
|
+
def get(self, key: str, default=None):
|
|
297
299
|
"""Dictionary-like get
|
|
298
300
|
|
|
299
301
|
Args:
|
|
@@ -314,12 +316,12 @@ class Settings:
|
|
|
314
316
|
Returns:
|
|
315
317
|
bool: True if the key exists
|
|
316
318
|
"""
|
|
317
|
-
return self.__lookup(key, check=True)
|
|
319
|
+
return bool(self.__lookup(key, check=True))
|
|
318
320
|
|
|
319
321
|
def __contains__(self, key: str) -> bool:
|
|
320
322
|
return self.has(key)
|
|
321
323
|
|
|
322
|
-
def __getitem__(self, key: str)
|
|
324
|
+
def __getitem__(self, key: str):
|
|
323
325
|
if not self.has(key):
|
|
324
326
|
raise KeyError(key)
|
|
325
327
|
|
|
@@ -371,6 +373,6 @@ class Settings:
|
|
|
371
373
|
|
|
372
374
|
for extension, name, directory, loader in search_info:
|
|
373
375
|
contents = loader(join(directory, name + extension))
|
|
374
|
-
Settings.__merge(settings, contents)
|
|
376
|
+
Settings.__merge(settings, contents if contents else {})
|
|
375
377
|
|
|
376
378
|
return settings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devopsdriver
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.52
|
|
4
4
|
Summary: DevOps tools
|
|
5
5
|
Author-email: Marc Page <marcallenpage@gmail.com>
|
|
6
6
|
License: This is free and unencumbered software released into the public domain.
|
|
@@ -51,7 +51,7 @@ Requires-Dist: keyring==25.6.0
|
|
|
51
51
|
Requires-Dist: setuptools==80.9.0
|
|
52
52
|
Requires-Dist: azure-devops==7.1.0b4
|
|
53
53
|
Requires-Dist: Mako==1.3.10
|
|
54
|
-
Requires-Dist: PyGithub==2.
|
|
54
|
+
Requires-Dist: PyGithub==2.8.1
|
|
55
55
|
Provides-Extra: dev
|
|
56
56
|
Requires-Dist: black>=24.3.0; extra == "dev"
|
|
57
57
|
Requires-Dist: pylint>=3.1.0; extra == "dev"
|
|
@@ -64,7 +64,7 @@ Dynamic: license-file
|
|
|
64
64
|
# devops-driver
|
|
65
65
|
|
|
66
66
|

|
|
67
|
-
[](https://pypi.org/project/devopsdriver/0.1.52/)
|
|
68
68
|
[](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
|
|
69
69
|
[](https://github.com/marcpage/devops-driver/graphs/contributors)
|
|
70
70
|
[](http://makeapullrequest.com)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
"""
|
|
3
|
+
"""Test work item query language"""
|
|
4
4
|
|
|
5
5
|
from datetime import date, datetime
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ from devopsdriver.azdo import GreaterThanOrEqual, LessThanOrEqual
|
|
|
13
13
|
|
|
14
14
|
def test_no_params() -> None:
|
|
15
15
|
"""Test empty, default wiql"""
|
|
16
|
-
assert str(Wiql()) == "SELECT [System.Id] FROM
|
|
16
|
+
assert str(Wiql()) == "SELECT [System.Id] FROM WorkItems", str(Wiql())
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def test_expressions() -> None:
|
|
@@ -43,7 +43,7 @@ def test_expressions() -> None:
|
|
|
43
43
|
.asof(start)
|
|
44
44
|
)
|
|
45
45
|
expected = (
|
|
46
|
-
"""SELECT [System.State], [System.Id] FROM
|
|
46
|
+
"""SELECT [System.State], [System.Id] FROM WorkItems """
|
|
47
47
|
+ """WHERE [System.State] = "New" AND [System.Title] IS EMPTY """
|
|
48
48
|
+ """AND [Microsoft.VSTS.Common.Priority] IS NOT EMPTY """
|
|
49
49
|
+ """AND [System.CreatedDate] > "06/30/2024" """
|
|
@@ -71,7 +71,7 @@ def test_in_and_not_in() -> None:
|
|
|
71
71
|
And(In("State", "New", "Ready for Development"), NotIn("Priority", 1, 2))
|
|
72
72
|
)
|
|
73
73
|
expected = (
|
|
74
|
-
"""SELECT [System.Id] FROM
|
|
74
|
+
"""SELECT [System.Id] FROM WorkItems WHERE [System.State] """
|
|
75
75
|
+ """IN ("New", "Ready for Development") AND [Microsoft.VSTS.Common.Priority] """
|
|
76
76
|
+ """NOT IN (1, 2)"""
|
|
77
77
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|