devopsdriver 0.1.53__tar.gz → 0.1.55__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.53 → devopsdriver-0.1.55}/PKG-INFO +3 -3
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/README.md +1 -1
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/__init__.py +1 -1
- devopsdriver-0.1.55/devopsdriver/dataobject.py +218 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver.egg-info/PKG-INFO +3 -3
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver.egg-info/requires.txt +1 -1
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/pyproject.toml +1 -1
- devopsdriver-0.1.55/tests/test_dataobject.py +420 -0
- devopsdriver-0.1.53/devopsdriver/dataobject.py +0 -63
- devopsdriver-0.1.53/tests/test_dataobject.py +0 -29
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/LICENSE +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/__init__.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/azureobject.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/builds/__init__.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/builds/build.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/builds/client.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/clients.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/pipeline/__init__.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/pipeline/client.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/pipeline/log.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/pipeline/pipeline.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/pipeline/run.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/timestamp.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/workitem/__init__.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/workitem/client.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/azdo/workitem/wiql.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/github/__init__.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/github/client.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/manage_settings.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/sendmail.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/settings.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/template.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver/templates/manage_settings.txt.mako +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver.egg-info/SOURCES.txt +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver.egg-info/dependency_links.txt +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver.egg-info/entry_points.txt +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/devopsdriver.egg-info/top_level.txt +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/setup.cfg +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_azureobject.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_build.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_build_client.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_clients.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_pipeline.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_pipeline_client.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_pipeline_run.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_timestamp.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_workitem_client.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_azure_workitem_wiql.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_manage_settings.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_sendmail.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/tests/test_settings.py +0 -0
- {devopsdriver-0.1.53 → devopsdriver-0.1.55}/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.55
|
|
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.
|
|
@@ -50,7 +50,7 @@ Requires-Dist: PyYAML==6.0.3
|
|
|
50
50
|
Requires-Dist: keyring==25.7.0
|
|
51
51
|
Requires-Dist: setuptools==82.0.1
|
|
52
52
|
Requires-Dist: azure-devops==7.1.0b4
|
|
53
|
-
Requires-Dist: Mako==1.3.
|
|
53
|
+
Requires-Dist: Mako==1.3.12
|
|
54
54
|
Requires-Dist: PyGithub==2.9.1
|
|
55
55
|
Provides-Extra: dev
|
|
56
56
|
Requires-Dist: black>=24.3.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.55/)
|
|
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.55/)
|
|
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)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Data Objects"""
|
|
4
|
+
|
|
5
|
+
from json import dumps
|
|
6
|
+
from re import fullmatch
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DataObject: # pylint: disable=too-few-public-methods
|
|
11
|
+
"""dict like object with fuzzy field matching"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, data: dict):
|
|
14
|
+
self.data = data
|
|
15
|
+
|
|
16
|
+
def _matches_field(self, name: str, field: str) -> bool:
|
|
17
|
+
name = name.lower()
|
|
18
|
+
field = field.lower()
|
|
19
|
+
|
|
20
|
+
if name == field:
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
if name == field.replace(".", "_"):
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
if name == field.split(".")[-1]:
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
def _parse_value(self, data: Any) -> Any:
|
|
32
|
+
if isinstance(data, dict):
|
|
33
|
+
return DataObject._Dict(self, data)
|
|
34
|
+
|
|
35
|
+
if isinstance(data, list):
|
|
36
|
+
return [self._parse_value(d) for d in data]
|
|
37
|
+
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
def _get_field(self, name: str, data: dict) -> Any:
|
|
41
|
+
assert name and data, f"name = {name} data = {data}"
|
|
42
|
+
found = [f for f in data if self._matches_field(name, f)]
|
|
43
|
+
assert len(found) in {0, 1}, found
|
|
44
|
+
|
|
45
|
+
if len(found) == 1:
|
|
46
|
+
return self._parse_value(data[found[0]])
|
|
47
|
+
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def __getattr__(self, name: str) -> Any:
|
|
51
|
+
return self._get_field(name, self.data)
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return dumps(self.data, indent=2)
|
|
55
|
+
|
|
56
|
+
def __repr__(self) -> str:
|
|
57
|
+
return dumps(self.data, indent=2)
|
|
58
|
+
|
|
59
|
+
def lookup(self, path: str, default: Any = None) -> Any:
|
|
60
|
+
"""
|
|
61
|
+
Resolves a custom path expression against the underlying data.
|
|
62
|
+
|
|
63
|
+
Supported syntax:
|
|
64
|
+
/key/subkey
|
|
65
|
+
.first
|
|
66
|
+
.last
|
|
67
|
+
.split(delimiter)
|
|
68
|
+
/(path=value)
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
/id
|
|
72
|
+
/fields/System.Title
|
|
73
|
+
/relations/(/attributes/name=Parent).first/url.split(/).last
|
|
74
|
+
/relations/(/attributes/name=Child)/url.split(/).last
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
tokens = self._tokenize(path)
|
|
78
|
+
current = self.data
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
for token in tokens:
|
|
82
|
+
current = self._apply_token(current, token)
|
|
83
|
+
|
|
84
|
+
except KeyError:
|
|
85
|
+
return default
|
|
86
|
+
|
|
87
|
+
return current
|
|
88
|
+
|
|
89
|
+
def _tokenize(self, path: str) -> list[str]:
|
|
90
|
+
"""Splits a path into tokens while respecting parentheses."""
|
|
91
|
+
|
|
92
|
+
path = path.strip("/")
|
|
93
|
+
|
|
94
|
+
tokens: list[str] = []
|
|
95
|
+
current: list[str] = []
|
|
96
|
+
depth = 0
|
|
97
|
+
|
|
98
|
+
for char in path:
|
|
99
|
+
if char == "/" and depth == 0:
|
|
100
|
+
if current:
|
|
101
|
+
tokens.append("".join(current))
|
|
102
|
+
current = []
|
|
103
|
+
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if char == "(":
|
|
107
|
+
depth += 1
|
|
108
|
+
elif char == ")":
|
|
109
|
+
depth -= 1
|
|
110
|
+
|
|
111
|
+
current.append(char)
|
|
112
|
+
|
|
113
|
+
if current:
|
|
114
|
+
tokens.append("".join(current))
|
|
115
|
+
|
|
116
|
+
return tokens
|
|
117
|
+
|
|
118
|
+
def _apply_token(self, value: Any, token: str) -> Any:
|
|
119
|
+
"""
|
|
120
|
+
Applies a token to the current value.
|
|
121
|
+
|
|
122
|
+
Supports chained expressions like:
|
|
123
|
+
(...).first
|
|
124
|
+
url.split(/).last.int
|
|
125
|
+
But maintains tokens like:
|
|
126
|
+
System.Title
|
|
127
|
+
"""
|
|
128
|
+
segments = token.split(".")
|
|
129
|
+
parts = []
|
|
130
|
+
|
|
131
|
+
while segments[-1] in ("first", "last", "int") or segments[-1].startswith(
|
|
132
|
+
"split"
|
|
133
|
+
):
|
|
134
|
+
parts.insert(0, segments.pop())
|
|
135
|
+
|
|
136
|
+
parts.insert(0, ".".join(segments))
|
|
137
|
+
current = value
|
|
138
|
+
group = False
|
|
139
|
+
|
|
140
|
+
for part in parts:
|
|
141
|
+
current, group = self._apply_part(current, part, group)
|
|
142
|
+
|
|
143
|
+
return current
|
|
144
|
+
|
|
145
|
+
def _apply_part( # pylint: disable=too-many-return-statements
|
|
146
|
+
self, value: Any, part: str, group: bool = False
|
|
147
|
+
) -> tuple[Any, bool]:
|
|
148
|
+
"""Applies a single operation."""
|
|
149
|
+
if part.startswith("(") and part.endswith(")"): # List filter
|
|
150
|
+
return self._filter_list(value, part[1:-1]), group
|
|
151
|
+
|
|
152
|
+
if part == "first": # first
|
|
153
|
+
if group:
|
|
154
|
+
return [v[0] for v in value], group
|
|
155
|
+
|
|
156
|
+
return value[0], False
|
|
157
|
+
|
|
158
|
+
if part == "last": # last
|
|
159
|
+
if group:
|
|
160
|
+
return [v[-1] for v in value], group
|
|
161
|
+
|
|
162
|
+
return value[-1], False
|
|
163
|
+
|
|
164
|
+
if part == "int": # int
|
|
165
|
+
if group:
|
|
166
|
+
return [int(v) for v in value], group
|
|
167
|
+
|
|
168
|
+
return int(value), group
|
|
169
|
+
|
|
170
|
+
# split(delimiter)
|
|
171
|
+
split_only_match = fullmatch(r"split\((.*?)\)", part)
|
|
172
|
+
|
|
173
|
+
if split_only_match:
|
|
174
|
+
delimiter = split_only_match.group(1)
|
|
175
|
+
|
|
176
|
+
if isinstance(value, list):
|
|
177
|
+
return [item.split(delimiter) for item in value], group
|
|
178
|
+
|
|
179
|
+
return value.split(delimiter), group
|
|
180
|
+
|
|
181
|
+
if isinstance(value, dict): # Dictionary lookup
|
|
182
|
+
return value[part], group
|
|
183
|
+
|
|
184
|
+
if isinstance(value, list): # Apply lookup to all items in a list
|
|
185
|
+
return [self._apply_part(item, part)[0] for item in value], True
|
|
186
|
+
|
|
187
|
+
raise ValueError(f"Cannot apply '{part}' to value: {value}")
|
|
188
|
+
|
|
189
|
+
def _filter_list(self, items: list[dict], expression: str) -> list[Any]:
|
|
190
|
+
"""
|
|
191
|
+
Filters a list using an expression.
|
|
192
|
+
|
|
193
|
+
Expression format:
|
|
194
|
+
/path=value
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
/attributes/name=Parent
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
path_expr, expected = expression.split("=", 1)
|
|
201
|
+
|
|
202
|
+
result = []
|
|
203
|
+
|
|
204
|
+
for item in items:
|
|
205
|
+
actual = DataObject(item).lookup(path_expr)
|
|
206
|
+
|
|
207
|
+
if str(actual) == expected:
|
|
208
|
+
result.append(item)
|
|
209
|
+
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
class _Dict(dict):
|
|
213
|
+
def __init__(self, dataobject, data: dict):
|
|
214
|
+
self.dataobject = dataobject
|
|
215
|
+
super().__init__(data)
|
|
216
|
+
|
|
217
|
+
def __getattr__(self, name: str) -> Any:
|
|
218
|
+
return self.dataobject._get_field(name, self)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devopsdriver
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.55
|
|
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.
|
|
@@ -50,7 +50,7 @@ Requires-Dist: PyYAML==6.0.3
|
|
|
50
50
|
Requires-Dist: keyring==25.7.0
|
|
51
51
|
Requires-Dist: setuptools==82.0.1
|
|
52
52
|
Requires-Dist: azure-devops==7.1.0b4
|
|
53
|
-
Requires-Dist: Mako==1.3.
|
|
53
|
+
Requires-Dist: Mako==1.3.12
|
|
54
54
|
Requires-Dist: PyGithub==2.9.1
|
|
55
55
|
Provides-Extra: dev
|
|
56
56
|
Requires-Dist: black>=24.3.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.55/)
|
|
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)
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Testing dataobject"""
|
|
4
|
+
|
|
5
|
+
from devopsdriver.dataobject import DataObject
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_dataobject_basic() -> None:
|
|
9
|
+
"""tests the basic dataobject"""
|
|
10
|
+
data = DataObject(
|
|
11
|
+
{
|
|
12
|
+
"Test": 5,
|
|
13
|
+
"system.Go": 12,
|
|
14
|
+
"fields": {"harry": 16, "people": [{"name": "john"}, 5, "test"]},
|
|
15
|
+
}
|
|
16
|
+
)
|
|
17
|
+
assert data.test == 5, data.test
|
|
18
|
+
assert data.go == 12, data.go
|
|
19
|
+
assert data.system_go == 12, data.system_go
|
|
20
|
+
assert data.fields.Harry == 16, data.fields.Harry
|
|
21
|
+
assert data.Harry is None, data.Harry
|
|
22
|
+
assert data.fields.people[0].Name == "john", data.fields.people[0].Name
|
|
23
|
+
assert data.fields.people[1] == 5, data.fields.people[1]
|
|
24
|
+
assert data.fields.people[2] == "test", data.fields.people[2]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_lookup() -> None:
|
|
28
|
+
"""Tests lookup"""
|
|
29
|
+
data = DataObject(
|
|
30
|
+
{
|
|
31
|
+
"url": "https://dev.azure.com/ni/94b22d7b-ad7b-4f5e-88f0-867910f91c94/"
|
|
32
|
+
+ "_apis/wit/workItems/956619/revisions/200",
|
|
33
|
+
"fields": {
|
|
34
|
+
"System.WorkItemType": "Initiative",
|
|
35
|
+
"System.State": "Active",
|
|
36
|
+
"System.Reason": "Moved to state Active",
|
|
37
|
+
"System.AssignedTo": {
|
|
38
|
+
"displayName": "Samant, Abhay",
|
|
39
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
40
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/"
|
|
41
|
+
+ "_apis/Identities/838da6d7-6336-68fa-9b48-053771a4d16f",
|
|
42
|
+
"_links": {
|
|
43
|
+
"avatar": {
|
|
44
|
+
"href": "https://dev.azure.com/ni/"
|
|
45
|
+
+ "_apis/GraphProfile/MemberAvatars/"
|
|
46
|
+
+ "aad.M2NkOWZmNWQtMDMwZC03NDNlLTk5OTUtZTc2MGFhZDRhYmM1"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"id": "838da6d7-6336-68fa-9b48-053771a4d16f",
|
|
50
|
+
"uniqueName": "abhay.samant@emerson.com",
|
|
51
|
+
"imageUrl": "https://dev.azure.com/ni/"
|
|
52
|
+
+ "_apis/GraphProfile/MemberAvatars/"
|
|
53
|
+
+ "aad.M2NkOWZmNWQtMDMwZC03NDNlLTk5OTUtZTc2MGFhZDRhYmM1",
|
|
54
|
+
"descriptor": "aad.M2NkOWZmNWQtMDMwZC03NDNlLTk5OTUtZTc2MGFhZDRhYmM1",
|
|
55
|
+
},
|
|
56
|
+
"System.CreatedDate": "2020-01-08T22:59:16.223Z",
|
|
57
|
+
"System.CreatedBy": {
|
|
58
|
+
"displayName": "Tillerson, Michael",
|
|
59
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
60
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/"
|
|
61
|
+
+ "_apis/Identities/dc42cd0e-ab09-4553-8516-9efe930b92a2",
|
|
62
|
+
"_links": {
|
|
63
|
+
"avatar": {
|
|
64
|
+
"href": "https://dev.azure.com/ni/"
|
|
65
|
+
+ "_apis/GraphProfile/MemberAvatars/"
|
|
66
|
+
+ "aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"id": "dc42cd0e-ab09-4553-8516-9efe930b92a2",
|
|
70
|
+
"uniqueName": "michael.tillerson@emerson.com",
|
|
71
|
+
"imageUrl": "https://dev.azure.com/ni/"
|
|
72
|
+
+ "_apis/GraphProfile/MemberAvatars/"
|
|
73
|
+
+ "aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2",
|
|
74
|
+
"descriptor": "aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2",
|
|
75
|
+
},
|
|
76
|
+
"System.ChangedDate": "2021-02-25T16:50:38.367Z",
|
|
77
|
+
"System.ChangedBy": {
|
|
78
|
+
"displayName": "SVC, tmrdazdoservice1",
|
|
79
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
80
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/"
|
|
81
|
+
+ "_apis/Identities/760b4082-c90f-6f25-986a-2e22b876ccd7",
|
|
82
|
+
"_links": {
|
|
83
|
+
"avatar": {
|
|
84
|
+
"href": "https://dev.azure.com/ni/"
|
|
85
|
+
+ "_apis/GraphProfile/MemberAvatars/"
|
|
86
|
+
+ "aad.ODg2NDQ0NGMtNWJlNy03ZWRkLTljOTEtZmJiNTE4MzFlZjFj"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"id": "760b4082-c90f-6f25-986a-2e22b876ccd7",
|
|
90
|
+
"uniqueName": "tm-rd-azdo-service-1@emerson.com",
|
|
91
|
+
"imageUrl": "https://dev.azure.com/ni/"
|
|
92
|
+
+ "_apis/GraphProfile/MemberAvatars/"
|
|
93
|
+
+ "aad.ODg2NDQ0NGMtNWJlNy03ZWRkLTljOTEtZmJiNTE4MzFlZjFj",
|
|
94
|
+
"descriptor": "aad.ODg2NDQ0NGMtNWJlNy03ZWRkLTljOTEtZmJiNTE4MzFlZjFj",
|
|
95
|
+
},
|
|
96
|
+
"System.CommentCount": 0,
|
|
97
|
+
"System.TeamProject": "DevCentral",
|
|
98
|
+
"System.AreaPath": "DevCentral\\Business Units\\ADG\\ElectroMag\\MMIC",
|
|
99
|
+
"System.IterationPath": "DevCentral",
|
|
100
|
+
"Microsoft.VSTS.Common.ActivatedDate": "2021-01-13T17:33:29.453Z",
|
|
101
|
+
"Microsoft.VSTS.Common.ActivatedBy": {
|
|
102
|
+
"displayName": "Samant, Abhay",
|
|
103
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
104
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/"
|
|
105
|
+
+ "_apis/Identities/838da6d7-6336-68fa-9b48-053771a4d16f",
|
|
106
|
+
"_links": {
|
|
107
|
+
"avatar": {
|
|
108
|
+
"href": "https://dev.azure.com/ni/"
|
|
109
|
+
+ "_apis/GraphProfile/MemberAvatars/"
|
|
110
|
+
+ "aad.M2NkOWZmNWQtMDMwZC03NDNlLTk5OTUtZTc2MGFhZDRhYmM1"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"id": "838da6d7-6336-68fa-9b48-053771a4d16f",
|
|
114
|
+
"uniqueName": "abhay.samant@emerson.com",
|
|
115
|
+
"imageUrl": "https://dev.azure.com/ni/"
|
|
116
|
+
+ "_apis/GraphProfile/MemberAvatars/"
|
|
117
|
+
+ "aad.M2NkOWZmNWQtMDMwZC03NDNlLTk5OTUtZTc2MGFhZDRhYmM1",
|
|
118
|
+
"descriptor": "aad.M2NkOWZmNWQtMDMwZC03NDNlLTk5OTUtZTc2MGFhZDRhYmM1",
|
|
119
|
+
},
|
|
120
|
+
"Microsoft.VSTS.Scheduling.TargetDate": "2021-09-30T05:00:00Z",
|
|
121
|
+
"Microsoft.VSTS.Scheduling.StartDate": "2021-01-13T17:21:25.887Z",
|
|
122
|
+
"Microsoft.VSTS.Common.StackRank": 36894149.0,
|
|
123
|
+
"WEF_C397CD3F9AD34F4CB8D9481E56BBB71C_Kanban.Column.Done": False,
|
|
124
|
+
"WEF_5ADB076CBE2147FC82A22EC1842CD4E9_Kanban.Column.Done": False,
|
|
125
|
+
"Custom.ExpectedStartDate": "2021-01-13T17:21:25.887Z",
|
|
126
|
+
"Custom.ExpectedEndDate": "2021-09-30T05:00:00Z",
|
|
127
|
+
"Custom.AssignedToDate": "2021-01-13T17:33:29Z",
|
|
128
|
+
"Custom.DaysUntilAssigned": 370.8,
|
|
129
|
+
"Custom.AllocationLevel": "Level 1",
|
|
130
|
+
"Custom.OfferingAffinity": "Cognitive RF Sensor Prototyping Testbed ",
|
|
131
|
+
"Custom.AeroDefGovAllocation": 100,
|
|
132
|
+
"WEF_E2F7AF0C84D84051A09966884CE567A9_Kanban.Column.Done": False,
|
|
133
|
+
"Custom.InitiativeType": "P&T Managed",
|
|
134
|
+
"Custom.IncludeonPoR": False,
|
|
135
|
+
"Custom.ScopeorQualityRisk": False,
|
|
136
|
+
"Custom.ScheduleRisk": False,
|
|
137
|
+
"Custom.DaysUntilActive": 370.8,
|
|
138
|
+
"System.Description": '<div><img src="https://dev.azure.com/ni/'
|
|
139
|
+
+ '94b22d7b-ad7b-4f5e-88f0-867910f91c94/"+"_apis/wit/attachments/'
|
|
140
|
+
+ '64027421-bbca-408e-a742-209aa15366a3?fileName=image.png" alt=Image><br></div>',
|
|
141
|
+
"Microsoft.VSTS.Common.AcceptanceCriteria": "<span>Measurement IP library"
|
|
142
|
+
+ "<br></span>"
|
|
143
|
+
+ "<div>Interactive Examples<br></div><div>For interactive use-case and demos"
|
|
144
|
+
+ "<br></div>"
|
|
145
|
+
+ "<div>Programming Examples<br></div><div>Demonstrate how to use the MMIC VIs"
|
|
146
|
+
+ "<br></div>"
|
|
147
|
+
+ "<div>Datasheet with Performance Metrics<br></div>"
|
|
148
|
+
+ "<span>Documentation of the system and recommendation about the "
|
|
149
|
+
+ "third-party components "
|
|
150
|
+
+ "(couplers, switches, cables, adapters and cal-kit)</span>",
|
|
151
|
+
"Custom.DeprecatedFields": "The Iterative planning team is currently "
|
|
152
|
+
+ "refactoring Initiatives and Epics. Expect changes to these two work items "
|
|
153
|
+
+ "over the next few months.The below "Legacy Fields" are under "
|
|
154
|
+
+ "consideration for removal. If these fields are needed, contact Green Rives.",
|
|
155
|
+
"WEF_5ADB076CBE2147FC82A22EC1842CD4E9_Kanban.Column": "Active",
|
|
156
|
+
"WEF_C397CD3F9AD34F4CB8D9481E56BBB71C_Kanban.Column": "Active",
|
|
157
|
+
"WEF_E2F7AF0C84D84051A09966884CE567A9_Kanban.Column": "Active",
|
|
158
|
+
"Microsoft.VSTS.Common.StateChangeDate": "2021-01-13T17:33:29Z",
|
|
159
|
+
"System.BoardColumn": "Active",
|
|
160
|
+
"System.BoardColumnDone": False,
|
|
161
|
+
"Custom.ParentID": 1081371,
|
|
162
|
+
"Custom.ParentTitle": "MMIC: SDP Dev Planning",
|
|
163
|
+
"System.Title": "MMIC Reference Architecture for PA/TRM Test Using 5841/5831",
|
|
164
|
+
"Microsoft.VSTS.Scheduling.StoryPoints": 390.6,
|
|
165
|
+
"Custom.CompletedStoryPoints": 180.0,
|
|
166
|
+
"System.Tags": "ADG Offering",
|
|
167
|
+
"System.Parent": 1081371,
|
|
168
|
+
},
|
|
169
|
+
"id": 956619,
|
|
170
|
+
"relations": [
|
|
171
|
+
{
|
|
172
|
+
"attributes": {"isLocked": False, "name": "Child"},
|
|
173
|
+
"rel": "System.LinkTypes.Hierarchy-Forward",
|
|
174
|
+
"url": "https://dev.azure.com/ni/94b22d7b-ad7b-4f5e-88f0-867910f91c94/"
|
|
175
|
+
+ "_apis/wit/workItems/1094328",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"attributes": {"isLocked": False, "name": "Child"},
|
|
179
|
+
"rel": "System.LinkTypes.Hierarchy-Forward",
|
|
180
|
+
"url": "https://dev.azure.com/ni/94b22d7b-ad7b-4f5e-88f0-867910f91c94/"
|
|
181
|
+
+ "_apis/wit/workItems/956628",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"attributes": {"isLocked": False, "name": "Child"},
|
|
185
|
+
"rel": "System.LinkTypes.Hierarchy-Forward",
|
|
186
|
+
"url": "https://dev.azure.com/ni/94b22d7b-ad7b-4f5e-88f0-867910f91c94/"
|
|
187
|
+
+ "_apis/wit/workItems/1198202",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"attributes": {"isLocked": False, "name": "Parent"},
|
|
191
|
+
"rel": "System.LinkTypes.Hierarchy-Reverse",
|
|
192
|
+
"url": "https://dev.azure.com/ni/94b22d7b-ad7b-4f5e-88f0-867910f91c94/"
|
|
193
|
+
+ "_apis/wit/workItems/1081371",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
"rev": 200,
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
assert data.lookup("/id") == 956619, data.lookup("/id")
|
|
201
|
+
assert (
|
|
202
|
+
data.lookup("/fields/System.Title")
|
|
203
|
+
== "MMIC Reference Architecture for PA/TRM Test Using 5841/5831"
|
|
204
|
+
), data.lookup("/fields/System.Title")
|
|
205
|
+
assert (
|
|
206
|
+
data.lookup("/relations/(/attributes/name=Parent).first/url.split(/).last.int")
|
|
207
|
+
== 1081371
|
|
208
|
+
), [data.lookup("/relations/(/attributes/name=Parent).first/url.split(/).last.int")]
|
|
209
|
+
assert data.lookup("/relations/(/attributes/name=Child)/url.split(/).last.int") == [
|
|
210
|
+
1094328,
|
|
211
|
+
956628,
|
|
212
|
+
1198202,
|
|
213
|
+
], [data.lookup("/relations/(/attributes/name=Child)/url.split(/).last.int")]
|
|
214
|
+
assert data.lookup("/fields/System.State") == "Active", data.lookup(
|
|
215
|
+
"/fields/System.State"
|
|
216
|
+
)
|
|
217
|
+
assert data.lookup("/fields/System.WorkItemType") == "Initiative", data.lookup(
|
|
218
|
+
"/fields/System.WorkItemType"
|
|
219
|
+
)
|
|
220
|
+
assert (
|
|
221
|
+
data.lookup("/fields/System.AreaPath")
|
|
222
|
+
== "DevCentral\\Business Units\\ADG\\ElectroMag\\MMIC"
|
|
223
|
+
), data.lookup("/fields/System.AreaPath")
|
|
224
|
+
assert data.lookup("/fields/System.IterationPath") == "DevCentral", data.lookup(
|
|
225
|
+
"/fields/System.IterationPath"
|
|
226
|
+
)
|
|
227
|
+
assert data.lookup("/fields/System.Tags") == "ADG Offering", data.lookup(
|
|
228
|
+
"/fields/System.Tags"
|
|
229
|
+
)
|
|
230
|
+
assert int(
|
|
231
|
+
10 * data.lookup("/fields/Microsoft.VSTS.Scheduling.StoryPoints")
|
|
232
|
+
) == int(10 * 390.6), data.lookup("/fields/Microsoft.VSTS.Scheduling.StoryPoints")
|
|
233
|
+
|
|
234
|
+
assert data.lookup("/relations/(/attributes/name=Child)/url.split(/).first") == [
|
|
235
|
+
"https:",
|
|
236
|
+
"https:",
|
|
237
|
+
"https:",
|
|
238
|
+
], [data.lookup("/relations/(/attributes/name=Child)/url.split(/).first")]
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
data.lookup("/fields/not there")
|
|
242
|
+
except KeyError:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
data.lookup("/fields/System.Title/not there")
|
|
247
|
+
except ValueError:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_lookup_missing() -> None:
|
|
252
|
+
"""Tests lookup when a key is missing"""
|
|
253
|
+
data = DataObject(
|
|
254
|
+
{
|
|
255
|
+
"url": "https://dev.azure.com/ni/94b22d7b-ad7b-4f5e-88f0-867910f91c94/_apis/"
|
|
256
|
+
+ "wit/workItems/949367/revisions/54",
|
|
257
|
+
"fields": {
|
|
258
|
+
"System.AreaPath": "DevCentral\\Business Units\\ADG\\SharedRepo\\MMSsc",
|
|
259
|
+
"System.TeamProject": "DevCentral",
|
|
260
|
+
"System.IterationPath": "DevCentral\\zzzArchive\\Cycles\\Cycle 17\\i77 "
|
|
261
|
+
+ "(ends 2020-02-28)",
|
|
262
|
+
"System.WorkItemType": "Research",
|
|
263
|
+
"System.State": "Closed",
|
|
264
|
+
"System.Reason": "Moved to state Closed",
|
|
265
|
+
"System.AssignedTo": {
|
|
266
|
+
"displayName": "Brown, Andy",
|
|
267
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
268
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/_apis/Identities/"
|
|
269
|
+
+ "d91becd5-72ba-6967-a84a-6a6708550e20",
|
|
270
|
+
"_links": {
|
|
271
|
+
"avatar": {
|
|
272
|
+
"href": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
273
|
+
+ "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl"
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
"id": "d91becd5-72ba-6967-a84a-6a6708550e20",
|
|
277
|
+
"uniqueName": "andy.c.brown@emerson.com",
|
|
278
|
+
"imageUrl": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
279
|
+
+ "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl",
|
|
280
|
+
"descriptor": "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl",
|
|
281
|
+
},
|
|
282
|
+
"System.CreatedDate": "2019-12-10T20:33:08.743Z",
|
|
283
|
+
"System.CreatedBy": {
|
|
284
|
+
"displayName": "Tillerson, Michael",
|
|
285
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
286
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/_apis/Identities/"
|
|
287
|
+
+ "dc42cd0e-ab09-4553-8516-9efe930b92a2",
|
|
288
|
+
"_links": {
|
|
289
|
+
"avatar": {
|
|
290
|
+
"href": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
291
|
+
+ "aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2"
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
"id": "dc42cd0e-ab09-4553-8516-9efe930b92a2",
|
|
295
|
+
"uniqueName": "michael.tillerson@emerson.com",
|
|
296
|
+
"imageUrl": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
297
|
+
+ "aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2",
|
|
298
|
+
"descriptor": "aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2",
|
|
299
|
+
},
|
|
300
|
+
"System.ChangedDate": "2026-05-07T16:20:55.96Z",
|
|
301
|
+
"System.ChangedBy": {
|
|
302
|
+
"displayName": "Tillerson, Michael",
|
|
303
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
304
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/_apis/Identities/"
|
|
305
|
+
+ "dc42cd0e-ab09-4553-8516-9efe930b92a2",
|
|
306
|
+
"_links": {
|
|
307
|
+
"avatar": {
|
|
308
|
+
"href": "https://dev.azure.com/ni/_apis/GraphProfile/"
|
|
309
|
+
+ "MemberAvatars/aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2"
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"id": "dc42cd0e-ab09-4553-8516-9efe930b92a2",
|
|
313
|
+
"uniqueName": "michael.tillerson@emerson.com",
|
|
314
|
+
"imageUrl": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
315
|
+
+ "aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2",
|
|
316
|
+
"descriptor": "aad.YWZkMzg2MTItMTAwMy03YmM1LWI1ODQtMTY1YTcwMmM1MmE2",
|
|
317
|
+
},
|
|
318
|
+
"System.CommentCount": 7,
|
|
319
|
+
"System.Title": "PA: RFSA: Test Bench Architecture (time boxed)",
|
|
320
|
+
"System.BoardColumn": "Closed",
|
|
321
|
+
"System.BoardColumnDone": False,
|
|
322
|
+
"Microsoft.VSTS.Common.StateChangeDate": "2019-12-10T20:33:08.743Z",
|
|
323
|
+
"Microsoft.VSTS.Common.ClosedDate": "2020-02-14T18:17:33.233Z",
|
|
324
|
+
"Microsoft.VSTS.Common.ValueArea": "Business",
|
|
325
|
+
"Microsoft.VSTS.Common.ClosedBy": {
|
|
326
|
+
"displayName": "Brown, Andy",
|
|
327
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
328
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/_apis/Identities/"
|
|
329
|
+
+ "d91becd5-72ba-6967-a84a-6a6708550e20",
|
|
330
|
+
"_links": {
|
|
331
|
+
"avatar": {
|
|
332
|
+
"href": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
333
|
+
+ "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl"
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
"id": "d91becd5-72ba-6967-a84a-6a6708550e20",
|
|
337
|
+
"uniqueName": "andy.c.brown@emerson.com",
|
|
338
|
+
"imageUrl": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
339
|
+
+ "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl",
|
|
340
|
+
"descriptor": "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl",
|
|
341
|
+
},
|
|
342
|
+
"Microsoft.VSTS.Common.ResolvedDate": "2020-02-06T03:21:28.327Z",
|
|
343
|
+
"Microsoft.VSTS.Common.ResolvedBy": {
|
|
344
|
+
"displayName": "Brown, Andy",
|
|
345
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
346
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/_apis/Identities/"
|
|
347
|
+
+ "d91becd5-72ba-6967-a84a-6a6708550e20",
|
|
348
|
+
"_links": {
|
|
349
|
+
"avatar": {
|
|
350
|
+
"href": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
351
|
+
+ "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl"
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
"id": "d91becd5-72ba-6967-a84a-6a6708550e20",
|
|
355
|
+
"uniqueName": "andy.c.brown@emerson.com",
|
|
356
|
+
"imageUrl": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
357
|
+
+ "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl",
|
|
358
|
+
"descriptor": "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl",
|
|
359
|
+
},
|
|
360
|
+
"Microsoft.VSTS.Common.StackRank": 388652161.0,
|
|
361
|
+
"Microsoft.VSTS.Scheduling.StoryPoints": 5.0,
|
|
362
|
+
"Custom.PendingValidationBy": {
|
|
363
|
+
"displayName": "Brown, Andy",
|
|
364
|
+
"url": "https://spsprodeus24.vssps.visualstudio.com/"
|
|
365
|
+
+ "Afae0922c-6afd-42ea-b8cb-e21a431ec8dd/_apis/Identities/"
|
|
366
|
+
+ "d91becd5-72ba-6967-a84a-6a6708550e20",
|
|
367
|
+
"_links": {
|
|
368
|
+
"avatar": {
|
|
369
|
+
"href": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
370
|
+
+ "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl"
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
"id": "d91becd5-72ba-6967-a84a-6a6708550e20",
|
|
374
|
+
"uniqueName": "andy.c.brown@emerson.com",
|
|
375
|
+
"imageUrl": "https://dev.azure.com/ni/_apis/GraphProfile/MemberAvatars/"
|
|
376
|
+
+ "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl",
|
|
377
|
+
"descriptor": "aad.ZDIxNmMzY2EtMDdiMi03MmQ1LWFjMWQtNmJhMThhZDlhN2Zl",
|
|
378
|
+
},
|
|
379
|
+
"Custom.PendingValidationDate": "2020-02-06T03:21:28.327Z",
|
|
380
|
+
"WEF_D8499809B15042DAABC202ECF51A67D8_Kanban.Column": "Closed",
|
|
381
|
+
"WEF_D8499809B15042DAABC202ECF51A67D8_Kanban.Column.Done": False,
|
|
382
|
+
"WEF_C7071942F5474A15BD600E74B97D7C7A_Kanban.Column": "Closed",
|
|
383
|
+
"WEF_C7071942F5474A15BD600E74B97D7C7A_Kanban.Column.Done": False,
|
|
384
|
+
"WEF_42B49516479B4AEEA0D80D89E8893C45_Kanban.Column": "Closed",
|
|
385
|
+
"WEF_42B49516479B4AEEA0D80D89E8893C45_Kanban.Column.Done": False,
|
|
386
|
+
"WEF_3E05701FE5EA4460876365252800813A_Kanban.Column": "Closed",
|
|
387
|
+
"WEF_3E05701FE5EA4460876365252800813A_Kanban.Column.Done": False,
|
|
388
|
+
"System.Description": "<div>As a developer, I need a testbench to measure and "
|
|
389
|
+
+ "validate these features and assess performance</div>",
|
|
390
|
+
"Microsoft.VSTS.Common.AcceptanceCriteria": "<ul><li>Expect documentation "
|
|
391
|
+
+ 'similar to <a href="https://dev.azure.com/ni/DevCentral/_wiki/wikis/'
|
|
392
|
+
+ 'AppCentral.wiki/8216/EV-Battery-Test-System">this EV Battery Test System '
|
|
393
|
+
+ "documentation</a></li><li>Purchase HW </li><ul><li>controller, chassis, "
|
|
394
|
+
+ "modules, cables, host computer</li><li><br></li></ul><li>Able to deploy SW to "
|
|
395
|
+
+ "DUT</li><li>Able to execute tests on DUT</li>"
|
|
396
|
+
+ "<li>Able to record test results</li>"
|
|
397
|
+
+ "<li><br></li><li>Eventually use DevOps Services</li>"
|
|
398
|
+
+ "<ul><li>Deployment Framework</li>"
|
|
399
|
+
+ "<li>Test Execution Framework</li>"
|
|
400
|
+
+ "<li><span>Test Reporting Framework</span></li></ul></ul>",
|
|
401
|
+
},
|
|
402
|
+
"id": 949367,
|
|
403
|
+
"rev": 54,
|
|
404
|
+
}
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
assert (
|
|
408
|
+
data.lookup("/relations/(/attributes/name=Parent).first/url.split(/).last.int")
|
|
409
|
+
is None
|
|
410
|
+
), [data.lookup("/relations/(/attributes/name=Parent).first/url.split(/).last.int")]
|
|
411
|
+
assert (
|
|
412
|
+
data.lookup("/relations/(/attributes/name=Child)/url.split(/).last.int", [])
|
|
413
|
+
== []
|
|
414
|
+
), [data.lookup("/relations/(/attributes/name=Child)/url.split(/).last.int", [])]
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
if __name__ == "__main__":
|
|
418
|
+
test_lookup_missing()
|
|
419
|
+
test_lookup()
|
|
420
|
+
test_dataobject_basic()
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
"""Data Objects"""
|
|
4
|
-
|
|
5
|
-
from json import dumps
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class DataObject: # pylint: disable=too-few-public-methods
|
|
9
|
-
"""dict like object with fuzzy field matching"""
|
|
10
|
-
|
|
11
|
-
def __init__(self, data: dict):
|
|
12
|
-
self.data = data
|
|
13
|
-
|
|
14
|
-
def _matches_field(self, name: str, field: str) -> bool:
|
|
15
|
-
name = name.lower()
|
|
16
|
-
field = field.lower()
|
|
17
|
-
|
|
18
|
-
if name == field:
|
|
19
|
-
return True
|
|
20
|
-
|
|
21
|
-
if name == field.replace(".", "_"):
|
|
22
|
-
return True
|
|
23
|
-
|
|
24
|
-
if name == field.split(".")[-1]:
|
|
25
|
-
return True
|
|
26
|
-
|
|
27
|
-
return False
|
|
28
|
-
|
|
29
|
-
def _parse_value(self, data: any) -> any:
|
|
30
|
-
if isinstance(data, dict):
|
|
31
|
-
return DataObject._Dict(self, data)
|
|
32
|
-
|
|
33
|
-
if isinstance(data, list):
|
|
34
|
-
return [self._parse_value(d) for d in data]
|
|
35
|
-
|
|
36
|
-
return data
|
|
37
|
-
|
|
38
|
-
def _get_field(self, name: str, data: dict) -> any:
|
|
39
|
-
assert name and data, f"name = {name} data = {data}"
|
|
40
|
-
found = [f for f in data if self._matches_field(name, f)]
|
|
41
|
-
assert len(found) in {0, 1}, found
|
|
42
|
-
|
|
43
|
-
if len(found) == 1:
|
|
44
|
-
return self._parse_value(data[found[0]])
|
|
45
|
-
|
|
46
|
-
return None
|
|
47
|
-
|
|
48
|
-
def __getattr__(self, name: str) -> any:
|
|
49
|
-
return self._get_field(name, self.data)
|
|
50
|
-
|
|
51
|
-
def __str__(self) -> str:
|
|
52
|
-
return dumps(self.data, indent=2)
|
|
53
|
-
|
|
54
|
-
def __repr__(self) -> str:
|
|
55
|
-
return dumps(self.data, indent=2)
|
|
56
|
-
|
|
57
|
-
class _Dict(dict):
|
|
58
|
-
def __init__(self, dataobject, data: dict):
|
|
59
|
-
self.dataobject = dataobject
|
|
60
|
-
super().__init__(data)
|
|
61
|
-
|
|
62
|
-
def __getattr__(self, name: str) -> any:
|
|
63
|
-
return self.dataobject._get_field(name, self)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
""" Testing dataobject """
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from devopsdriver.dataobject import DataObject
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def test_dataobject_basic() -> None:
|
|
10
|
-
"""tests the basic dataobject"""
|
|
11
|
-
data = DataObject(
|
|
12
|
-
{
|
|
13
|
-
"Test": 5,
|
|
14
|
-
"system.Go": 12,
|
|
15
|
-
"fields": {"harry": 16, "people": [{"name": "john"}, 5, "test"]},
|
|
16
|
-
}
|
|
17
|
-
)
|
|
18
|
-
assert data.test == 5, data.test
|
|
19
|
-
assert data.go == 12, data.go
|
|
20
|
-
assert data.system_go == 12, data.system_go
|
|
21
|
-
assert data.fields.Harry == 16, data.fields.Harry
|
|
22
|
-
assert data.Harry is None, data.Harry
|
|
23
|
-
assert data.fields.people[0].Name == "john", data.fields.people[0].Name
|
|
24
|
-
assert data.fields.people[1] == 5, data.fields.people[1]
|
|
25
|
-
assert data.fields.people[2] == "test", data.fields.people[2]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if __name__ == "__main__":
|
|
29
|
-
test_dataobject_basic()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|