devopsdriver 0.1.40__tar.gz → 0.1.42__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.
Files changed (44) hide show
  1. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/PKG-INFO +4 -4
  2. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/README.md +1 -1
  3. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/__init__.py +1 -1
  4. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/azdo/__init__.py +2 -2
  5. devopsdriver-0.1.42/devopsdriver/azdo/azureobject.py +32 -0
  6. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/azdo/clients.py +4 -1
  7. devopsdriver-0.1.42/devopsdriver/azdo/pipeline/__init__.py +0 -0
  8. devopsdriver-0.1.42/devopsdriver/azdo/pipeline/client.py +30 -0
  9. devopsdriver-0.1.42/devopsdriver/azdo/pipeline/log.py +28 -0
  10. devopsdriver-0.1.42/devopsdriver/azdo/pipeline/pipeline.py +35 -0
  11. devopsdriver-0.1.42/devopsdriver/azdo/pipeline/run.py +45 -0
  12. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/azdo/workitem/client.py +3 -3
  13. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/azdo/workitem/wiql.py +36 -1
  14. devopsdriver-0.1.42/devopsdriver/dataobject.py +55 -0
  15. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver.egg-info/PKG-INFO +4 -4
  16. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver.egg-info/SOURCES.txt +12 -2
  17. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver.egg-info/requires.txt +2 -2
  18. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/pyproject.toml +5 -2
  19. devopsdriver-0.1.40/tests/test_azure_workitem.py → devopsdriver-0.1.42/tests/test_azure_azureobject.py +4 -4
  20. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/tests/test_azure_clients.py +4 -0
  21. devopsdriver-0.1.42/tests/test_azure_pipeline.py +47 -0
  22. devopsdriver-0.1.42/tests/test_azure_pipeline_client.py +35 -0
  23. devopsdriver-0.1.42/tests/test_azure_pipeline_run.py +68 -0
  24. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/tests/test_azure_timestamp.py +1 -0
  25. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/tests/test_azure_workitem_wiql.py +15 -1
  26. devopsdriver-0.1.42/tests/test_dataobject.py +29 -0
  27. devopsdriver-0.1.40/devopsdriver/azdo/workitem/workitem.py +0 -68
  28. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/LICENSE +0 -0
  29. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/azdo/timestamp.py +0 -0
  30. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/azdo/workitem/__init__.py +0 -0
  31. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/manage_settings.py +0 -0
  32. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/sendmail.py +0 -0
  33. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/settings.py +0 -0
  34. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/template.py +0 -0
  35. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver/templates/manage_settings.txt.mako +0 -0
  36. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver.egg-info/dependency_links.txt +0 -0
  37. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver.egg-info/entry_points.txt +0 -0
  38. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/devopsdriver.egg-info/top_level.txt +0 -0
  39. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/setup.cfg +0 -0
  40. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/tests/test_azure_workitem_client.py +0 -0
  41. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/tests/test_manage_settings.py +0 -0
  42. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/tests/test_sendmail.py +0 -0
  43. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/tests/test_settings.py +0 -0
  44. {devopsdriver-0.1.40 → devopsdriver-0.1.42}/tests/test_template.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devopsdriver
3
- Version: 0.1.40
3
+ Version: 0.1.42
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.
@@ -47,8 +47,8 @@ Requires-Python: >=3.10
47
47
  Description-Content-Type: text/markdown
48
48
  License-File: LICENSE
49
49
  Requires-Dist: PyYAML==6.0.1
50
- Requires-Dist: keyring==25.0.0
51
- Requires-Dist: setuptools==69.0.2
50
+ Requires-Dist: keyring==25.1.0
51
+ Requires-Dist: setuptools==69.2.0
52
52
  Requires-Dist: azure-devops==7.1.0b4
53
53
  Requires-Dist: Mako==1.3.2
54
54
  Provides-Extra: dev
@@ -60,7 +60,7 @@ Requires-Dist: coverage>=7.4.4; extra == "test"
60
60
  Provides-Extra: doc
61
61
 
62
62
  ![status sheild](https://img.shields.io/static/v1?label=status&message=beta&color=blue&style=plastic)
63
- [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.40&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.40/)
63
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.42&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.42/)
64
64
  [![GitHub](https://img.shields.io/github/license/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
65
65
  [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
66
66
  [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
@@ -1,5 +1,5 @@
1
1
  ![status sheild](https://img.shields.io/static/v1?label=status&message=beta&color=blue&style=plastic)
2
- [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.40&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.40/)
2
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.42&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.42/)
3
3
  [![GitHub](https://img.shields.io/github/license/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
4
4
  [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
5
5
  [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
@@ -6,6 +6,6 @@ from .template import Template
6
6
  from .azdo.clients import Azure
7
7
 
8
8
 
9
- __version__ = "0.1.40"
9
+ __version__ = "0.1.42"
10
10
  __author__ = "Marc Page"
11
11
  __credits__ = ""
@@ -1,11 +1,11 @@
1
1
  """ initialize azure module """
2
2
 
3
3
  # re export symbols for easier use
4
+ from .azureobject import AzureObject
4
5
  from .clients import Azure
5
6
  from .timestamp import Timestamp
6
7
 
7
- from .workitem.workitem import WorkItem
8
8
  from .workitem.wiql import Wiql, Value, Field
9
- from .workitem.wiql import Ascending, Descending, And, Or
9
+ from .workitem.wiql import Ascending, Descending, And, Or, In, NotIn
10
10
  from .workitem.wiql import Equal, NotEqual, LessThanOrEqual, GreaterThanOrEqual
11
11
  from .workitem.wiql import IsEmpty, IsNotEmpty, LessThan, GreaterThan
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """ An Azure Devops WorkItem """
5
+
6
+
7
+ from msrest.serialization import Model
8
+
9
+ from devopsdriver.azdo.timestamp import Timestamp
10
+ from devopsdriver.dataobject import DataObject
11
+
12
+
13
+ class AzureObject(DataObject): # pylint: disable=too-few-public-methods
14
+ """Azure WorkItem"""
15
+
16
+ def __init__(self, azure_object: Model):
17
+ self.raw = azure_object
18
+ super().__init__(self.raw.as_dict())
19
+
20
+ def _parse_value(self, data: any) -> any:
21
+ if isinstance(data, str) and Timestamp.is_timestamp(data):
22
+ return Timestamp(data)
23
+
24
+ return super()._parse_value(data)
25
+
26
+ def _get_field(self, name: str, data: dict) -> any:
27
+ value = super()._get_field(name, data)
28
+
29
+ if value is None and "fields" in data:
30
+ return self._get_field(name, data["fields"])
31
+
32
+ return value
@@ -13,6 +13,7 @@ from msrest.authentication import BasicAuthentication as MSBasicAuthentication
13
13
 
14
14
  from devopsdriver.settings import Settings
15
15
  from devopsdriver.azdo.workitem.client import Client as WIClient
16
+ from devopsdriver.azdo.pipeline.client import Client as PLClient
16
17
 
17
18
 
18
19
  # for testing
@@ -44,9 +45,11 @@ class Azure: # pylint: disable=too-few-public-methods
44
45
  token = settings["azure.token"] if token is None else token
45
46
  self.connection = CONNECTION(base_url=url, creds=AUTHENTICATION("", token))
46
47
  client_calls = {
47
- "workitem": self.connection.clients_v7_1.get_work_item_tracking_client
48
+ "workitem": self.connection.clients_v7_1.get_work_item_tracking_client,
49
+ "pipeline": self.connection.clients_v7_1.get_pipelines_client,
48
50
  }
49
51
  self.workitem = WIClient(Azure.__client("workitem", clients, client_calls))
52
+ self.pipeline = PLClient(Azure.__client("pipeline", clients, client_calls))
50
53
 
51
54
  @staticmethod
52
55
  def __client(name: str, clients: dict, calls: dict) -> any:
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """ Pipeline client """
5
+
6
+
7
+ from azure.devops.v7_1.pipelines import PipelinesClient
8
+
9
+ from .pipeline import Pipeline
10
+
11
+
12
+ class Client: # pylint: disable=too-few-public-methods
13
+ """Pipeline Client wrapper"""
14
+
15
+ def __init__(self, client: PipelinesClient):
16
+ self.client = client
17
+
18
+ def list(self, project: str) -> list[Pipeline]:
19
+ """List the pipelines
20
+
21
+ Args:
22
+ project (str): The project to list
23
+
24
+ Returns:
25
+ list[Pipeline]: The list of pipeline objects
26
+ """
27
+ return [
28
+ Pipeline(self.client, project, p)
29
+ for p in self.client.list_pipelines(project)
30
+ ]
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """ Azure Pipeline Run Log """
5
+
6
+
7
+ from azure.devops.v7_1.pipelines.models import Log as AzureLog
8
+
9
+ from requests import get as get_url
10
+
11
+ from devopsdriver.azdo.azureobject import AzureObject
12
+
13
+
14
+ GET_URL = get_url
15
+
16
+
17
+ class Log(AzureObject): # pylint: disable=too-few-public-methods
18
+ """Log fields:
19
+ created_on ("2024-04-08T23:00:59.020Z")
20
+ id (8)
21
+ last_changed_on ("2024-04-08T23:00:59.160Z")
22
+ line_count (4)
23
+ url ("https://dev.azure.com/Org/<guid>/_apis/pipelines/1/runs/10/logs/8")
24
+ """
25
+
26
+ def __init__(self, log: AzureLog):
27
+ self.text = GET_URL(log.signed_content.url, timeout=1).text
28
+ super().__init__(log)
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """ Azure Pipeline """
5
+
6
+
7
+ from azure.devops.v7_1.pipelines.models import Pipeline as AzurePipeline
8
+ from azure.devops.v7_1.pipelines import PipelinesClient
9
+
10
+ from devopsdriver.azdo import AzureObject
11
+
12
+ from .run import Run
13
+
14
+
15
+ class Pipeline(AzureObject): # pylint: disable=too-few-public-methods
16
+ """Pipeline fields:
17
+ folder ("\\")
18
+ id (1)
19
+ name (DevOps)
20
+ revision (1),
21
+ _links ({}),
22
+ url (https://dev.azure.com/Org/<guid>/_apis/pipelines/1?revision=1)
23
+ """
24
+
25
+ def __init__(self, client: PipelinesClient, project: str, pipeline: AzurePipeline):
26
+ self.client = client
27
+ self.project = project
28
+ super().__init__(pipeline)
29
+
30
+ def get_runs(self):
31
+ """Gets the top 10,000 runs for this pipeline"""
32
+ return [
33
+ Run(self.client, self.project, self.raw, r)
34
+ for r in self.client.list_runs(self.project, self.id)
35
+ ]
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """ Pipeline Run """
5
+
6
+
7
+ from azure.devops.v7_1.pipelines.models import Pipeline
8
+ from azure.devops.v7_1.pipelines.models import Run as AzureRun
9
+ from azure.devops.v7_1.pipelines import PipelinesClient
10
+
11
+ from devopsdriver.azdo import AzureObject
12
+
13
+ from .log import Log
14
+
15
+
16
+ class Run(AzureObject): # pylint: disable=too-few-public-methods
17
+ """Run fields:
18
+ id (1)
19
+ name ("20240325.1")
20
+ _links ({})
21
+ created_date ("2024-03-25T23:53:38.503126Z")
22
+ finished_date ("2024-03-25T23:53:43.467565Z")
23
+ pipeline (see Pipeline)
24
+ result ("failed")
25
+ state ("completed")
26
+ template_parameters ({})
27
+ url ("https://dev.azure.com/Org/<guid>/_apis/pipelines/1/runs/1")
28
+ """
29
+
30
+ def __init__(
31
+ self, client: PipelinesClient, project: str, pipeline: Pipeline, run: AzureRun
32
+ ):
33
+ self.client = client
34
+ self.project = project
35
+ self.pipeline = pipeline
36
+ super().__init__(run)
37
+
38
+ def logs(self):
39
+ """Get Logs for the run"""
40
+ return [
41
+ Log(l)
42
+ for l in self.client.list_logs(
43
+ self.project, self.pipeline.id, self.raw.id, expand="signedContent"
44
+ )
45
+ ]
@@ -8,7 +8,7 @@ from azure.devops.v7_1.work_item_tracking.models import Wiql as AzureWiql
8
8
  from azure.devops.v7_1.work_item_tracking.models import WorkItem as AzureWorkItem
9
9
  from azure.devops.v7_1.work_item_tracking.models import TeamContext
10
10
  from azure.devops.v7_1.work_item_tracking.models import WorkItemQueryResult
11
- from devopsdriver.azdo.workitem.workitem import WorkItem
11
+ from devopsdriver.azdo.azureobject import AzureObject
12
12
  from devopsdriver.azdo.workitem.wiql import Wiql
13
13
 
14
14
 
@@ -75,7 +75,7 @@ class Client:
75
75
  # columns: list of name, reference_name, url
76
76
  return [i.id for i in found.work_items]
77
77
 
78
- def find(self, wiql: Wiql | str, top: int = None) -> list[list[WorkItem]]:
78
+ def find(self, wiql: Wiql | str, top: int = None) -> list[list[AzureObject]]:
79
79
  """Gets the full history of items found in a WIQL search
80
80
 
81
81
  Args:
@@ -86,5 +86,5 @@ class Client:
86
86
  list[list[WorkItem]]: List of work items, each is a history of work items
87
87
  """
88
88
  return [
89
- [WorkItem(e) for e in self.history(i)] for i in self.find_ids(wiql, top)
89
+ [AzureObject(e) for e in self.history(i)] for i in self.find_ids(wiql, top)
90
90
  ]
@@ -133,15 +133,50 @@ class Compare: # pylint: disable=too-few-public-methods
133
133
  field: Field | str,
134
134
  value: Value | str | date | datetime | int | float,
135
135
  operator: str,
136
+ value_is_computed=False,
136
137
  ):
137
138
  self.left = field if isinstance(field, Field) else Field(field)
138
- self.right = value if isinstance(value, Value) else Value(value)
139
+ self.right = (
140
+ value if value_is_computed or isinstance(value, Value) else Value(value)
141
+ )
139
142
  self.operator = operator
140
143
 
141
144
  def __str__(self) -> str:
142
145
  return f"{str(self.left)} {self.operator} {str(self.right)}"
143
146
 
144
147
 
148
+ class In(Compare): # pylint: disable=too-few-public-methods
149
+ """checks for field in a list of values"""
150
+
151
+ def __init__(
152
+ self,
153
+ field: Field | str,
154
+ *values: list[Value | str | date | datetime | int | float],
155
+ ):
156
+ super().__init__(
157
+ field,
158
+ f"({', '.join(str(v if isinstance(v, Value) else Value(v)) for v in values)})",
159
+ "IN",
160
+ value_is_computed=True,
161
+ )
162
+
163
+
164
+ class NotIn(Compare): # pylint: disable=too-few-public-methods
165
+ """checks for field in a list of values"""
166
+
167
+ def __init__(
168
+ self,
169
+ field: Field | str,
170
+ *values: list[Value | str | date | datetime | int | float],
171
+ ):
172
+ super().__init__(
173
+ field,
174
+ f"({', '.join(str(v if isinstance(v, Value) else Value(v)) for v in values)})",
175
+ "NOT IN",
176
+ value_is_computed=True,
177
+ )
178
+
179
+
145
180
  class Equal(Compare): # pylint: disable=too-few-public-methods
146
181
  """checks for equality"""
147
182
 
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """ Data Objects """
4
+
5
+
6
+ class DataObject: # pylint: disable=too-few-public-methods
7
+ """dict like object with fuzzy field matching"""
8
+
9
+ def __init__(self, data: dict):
10
+ self.data = data
11
+
12
+ def _matches_field(self, name: str, field: str) -> bool:
13
+ name = name.lower()
14
+ field = field.lower()
15
+
16
+ if name == field:
17
+ return True
18
+
19
+ if name == field.replace(".", "_"):
20
+ return True
21
+
22
+ if name == field.split(".")[-1]:
23
+ return True
24
+
25
+ return False
26
+
27
+ def _parse_value(self, data: any) -> any:
28
+ if isinstance(data, dict):
29
+ return DataObject._Dict(self, data)
30
+
31
+ if isinstance(data, list):
32
+ return [self._parse_value(d) for d in data]
33
+
34
+ return data
35
+
36
+ def _get_field(self, name: str, data: dict) -> any:
37
+ assert name and data, f"name = {name} data = {data}"
38
+ found = [f for f in data if self._matches_field(name, f)]
39
+ assert len(found) in {0, 1}, found
40
+
41
+ if len(found) == 1:
42
+ return self._parse_value(data[found[0]])
43
+
44
+ return None
45
+
46
+ def __getattr__(self, name: str) -> any:
47
+ return self._get_field(name, self.data)
48
+
49
+ class _Dict(dict):
50
+ def __init__(self, dataobject, data: dict):
51
+ self.dataobject = dataobject
52
+ super().__init__(data)
53
+
54
+ def __getattr__(self, name: str) -> any:
55
+ return self.dataobject._get_field(name, self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devopsdriver
3
- Version: 0.1.40
3
+ Version: 0.1.42
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.
@@ -47,8 +47,8 @@ Requires-Python: >=3.10
47
47
  Description-Content-Type: text/markdown
48
48
  License-File: LICENSE
49
49
  Requires-Dist: PyYAML==6.0.1
50
- Requires-Dist: keyring==25.0.0
51
- Requires-Dist: setuptools==69.0.2
50
+ Requires-Dist: keyring==25.1.0
51
+ Requires-Dist: setuptools==69.2.0
52
52
  Requires-Dist: azure-devops==7.1.0b4
53
53
  Requires-Dist: Mako==1.3.2
54
54
  Provides-Extra: dev
@@ -60,7 +60,7 @@ Requires-Dist: coverage>=7.4.4; extra == "test"
60
60
  Provides-Extra: doc
61
61
 
62
62
  ![status sheild](https://img.shields.io/static/v1?label=status&message=beta&color=blue&style=plastic)
63
- [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.40&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.40/)
63
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.42&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.42/)
64
64
  [![GitHub](https://img.shields.io/github/license/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
65
65
  [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
66
66
  [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  devopsdriver/__init__.py
5
+ devopsdriver/dataobject.py
5
6
  devopsdriver/manage_settings.py
6
7
  devopsdriver/sendmail.py
7
8
  devopsdriver/settings.py
@@ -13,18 +14,27 @@ devopsdriver.egg-info/entry_points.txt
13
14
  devopsdriver.egg-info/requires.txt
14
15
  devopsdriver.egg-info/top_level.txt
15
16
  devopsdriver/azdo/__init__.py
17
+ devopsdriver/azdo/azureobject.py
16
18
  devopsdriver/azdo/clients.py
17
19
  devopsdriver/azdo/timestamp.py
20
+ devopsdriver/azdo/pipeline/__init__.py
21
+ devopsdriver/azdo/pipeline/client.py
22
+ devopsdriver/azdo/pipeline/log.py
23
+ devopsdriver/azdo/pipeline/pipeline.py
24
+ devopsdriver/azdo/pipeline/run.py
18
25
  devopsdriver/azdo/workitem/__init__.py
19
26
  devopsdriver/azdo/workitem/client.py
20
27
  devopsdriver/azdo/workitem/wiql.py
21
- devopsdriver/azdo/workitem/workitem.py
22
28
  devopsdriver/templates/manage_settings.txt.mako
29
+ tests/test_azure_azureobject.py
23
30
  tests/test_azure_clients.py
31
+ tests/test_azure_pipeline.py
32
+ tests/test_azure_pipeline_client.py
33
+ tests/test_azure_pipeline_run.py
24
34
  tests/test_azure_timestamp.py
25
- tests/test_azure_workitem.py
26
35
  tests/test_azure_workitem_client.py
27
36
  tests/test_azure_workitem_wiql.py
37
+ tests/test_dataobject.py
28
38
  tests/test_manage_settings.py
29
39
  tests/test_sendmail.py
30
40
  tests/test_settings.py
@@ -1,6 +1,6 @@
1
1
  PyYAML==6.0.1
2
- keyring==25.0.0
3
- setuptools==69.0.2
2
+ keyring==25.1.0
3
+ setuptools==69.2.0
4
4
  azure-devops==7.1.0b4
5
5
  Mako==1.3.2
6
6
 
@@ -7,8 +7,8 @@ dynamic = ["version"]
7
7
  requires-python = ">= 3.10"
8
8
  dependencies = [
9
9
  "PyYAML==6.0.1",
10
- "keyring==25.0.0",
11
- "setuptools==69.0.2", # neded for azure-devops to use 7.1 API
10
+ "keyring==25.1.0",
11
+ "setuptools==69.2.0", # neded for azure-devops to use 7.1 API
12
12
  "azure-devops==7.1.0b4",
13
13
  "Mako==1.3.2",
14
14
  ]
@@ -34,6 +34,9 @@ settings = "devopsdriver.manage_settings:main"
34
34
  [tool.setuptools.package-data]
35
35
  "*" = ["*.mako"]
36
36
 
37
+ [tool.setuptools.packages.find]
38
+ include = ["devopsdriver*"]
39
+
37
40
  [project.optional-dependencies]
38
41
  dev = [
39
42
  "black>=24.3.0",
@@ -2,10 +2,10 @@
2
2
 
3
3
  """ Test work item """
4
4
 
5
- from devopsdriver.azdo import WorkItem
5
+ from devopsdriver.azdo import AzureObject
6
6
 
7
7
 
8
- class MockAzureWorkItem:
8
+ class MockAzureWorkItem: # pylint: disable=too-few-public-methods
9
9
  """mock out work item"""
10
10
 
11
11
  def as_dict(self):
@@ -76,7 +76,7 @@ class MockAzureWorkItem:
76
76
 
77
77
  def test_workitem() -> None:
78
78
  """test basic work item"""
79
- wi = WorkItem(MockAzureWorkItem())
79
+ wi = AzureObject(MockAzureWorkItem())
80
80
  assert wi.id == 5, wi.id
81
81
  assert wi.ID == 5, wi.ID
82
82
  assert wi.workitemtype == "User Story", wi.workitemtype
@@ -87,7 +87,7 @@ def test_workitem() -> None:
87
87
 
88
88
  def test_timestamp() -> None:
89
89
  """test timestamps"""
90
- wi = WorkItem(MockAzureWorkItem())
90
+ wi = AzureObject(MockAzureWorkItem())
91
91
  assert wi.StateChangeDate.to_string() == "2023-11-16T03:12:32.94Z"
92
92
  assert wi.CreatedDate.to_string() == "2023-11-16T03:12:32.94Z"
93
93
  assert wi.ChangedDate.to_string() == "2023-11-16T03:12:32.94Z"
@@ -28,6 +28,10 @@ class MockConnection: # pylint: disable=too-few-public-methods
28
28
  """fakes getting work item client"""
29
29
  return "work_item_tracking_client"
30
30
 
31
+ def get_pipelines_client(self) -> str:
32
+ """mocks getting pipeline client"""
33
+ return "get_pipelines_client"
34
+
31
35
  self.clients_v7_1 = Clients71()
32
36
 
33
37
 
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """ Test Pipeline """
4
+
5
+ from devopsdriver.azdo.pipeline.pipeline import Pipeline
6
+
7
+
8
+ class MockRun: # pylint: disable=too-few-public-methods
9
+ """moc out a run"""
10
+
11
+ def __init__(self, value: str):
12
+ self.value = value
13
+
14
+ def as_dict(self):
15
+ """mock out list_runs method"""
16
+ return {"id": self.value}
17
+
18
+
19
+ class MockPipelineClient:
20
+ """mock pipeline client"""
21
+
22
+ def as_dict(self):
23
+ """mock out as_dict method"""
24
+ return {}
25
+
26
+ def list_runs(self, project: str, pipeline_id: int):
27
+ """mock out list_runs method"""
28
+ return [MockRun(project), MockRun(pipeline_id)]
29
+
30
+
31
+ class MockPipeline: # pylint: disable=too-few-public-methods
32
+ """mock out Azure pipeline object"""
33
+
34
+ def as_dict(self):
35
+ """mock out list_runs method"""
36
+ return {"id": 5}
37
+
38
+
39
+ def test_pipeline() -> None:
40
+ """tests basic pipeline functionality"""
41
+ pipeline = Pipeline(MockPipelineClient(), "project", MockPipeline())
42
+ runs = pipeline.get_runs()
43
+ assert len(runs) == 2, runs
44
+
45
+
46
+ if __name__ == "__main__":
47
+ test_pipeline()
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """ Test Pipeline Client"""
4
+
5
+ from devopsdriver.azdo.pipeline.client import Client
6
+
7
+
8
+ class MockAzurePipeline: # pylint: disable=too-few-public-methods
9
+ """mock azure pipeline object"""
10
+
11
+ def __init__(self, project: str):
12
+ self.project = project
13
+
14
+ def as_dict(self):
15
+ """mock as_dict"""
16
+ return {"project": self.project}
17
+
18
+
19
+ class MockAzurePipelinesClient: # pylint: disable=too-few-public-methods
20
+ """mock pipelines client"""
21
+
22
+ def list_pipelines(self, project: str):
23
+ """mock list_pipelines"""
24
+ return [MockAzurePipeline(f"{project} 1"), MockAzurePipeline(f"{project} 2")]
25
+
26
+
27
+ def test_client() -> None:
28
+ """tests the pipeline client"""
29
+ client = Client(MockAzurePipelinesClient())
30
+ pipelines = client.list("project")
31
+ assert len(pipelines) == 2, pipelines
32
+
33
+
34
+ if __name__ == "__main__":
35
+ test_client()
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """ Test pipeline run """
4
+
5
+ from types import SimpleNamespace
6
+
7
+ from devopsdriver.azdo.pipeline.run import Run
8
+ from devopsdriver.azdo.pipeline import log
9
+
10
+
11
+ class MockAzureRun: # pylint: disable=too-few-public-methods
12
+ """Mock azure run object"""
13
+
14
+ def __init__(self, run_id):
15
+ self.id = run_id
16
+
17
+ def as_dict(self):
18
+ """mock as_dict"""
19
+ return {"id": 48}
20
+
21
+
22
+ class MockLog: # pylint: disable=too-few-public-methods
23
+ """mock azure log object"""
24
+
25
+ def __init__(self, text):
26
+ self.text = text
27
+ self.signed_content = SimpleNamespace(url=SimpleNamespace(text="some dumb url"))
28
+
29
+ def as_dict(self):
30
+ """mock as_dict"""
31
+ return {"id": 22}
32
+
33
+
34
+ class MockPipelineClient: # pylint: disable=too-few-public-methods
35
+ """mock azure pipelines client"""
36
+
37
+ def __init__(self):
38
+ self.project = None
39
+ self.pipeline_id = None
40
+ self.run_id = None
41
+ self.expand = None
42
+
43
+ def list_logs(self, project, pipeline_id, run_id, expand):
44
+ """mock list_logs"""
45
+ self.project = project
46
+ self.pipeline_id = pipeline_id
47
+ self.run_id = run_id
48
+ self.expand = expand
49
+ return [MockLog("log 1"), MockLog("log 2")]
50
+
51
+
52
+ class MockPipeline: # pylint: disable=too-few-public-methods
53
+ """mock pipeline object"""
54
+
55
+ def __init__(self, pipeline_id):
56
+ self.id = pipeline_id
57
+
58
+
59
+ def test_basic() -> None:
60
+ """test basic run and log"""
61
+ log.GET_URL = lambda x, timeout: x
62
+ run = Run(MockPipelineClient(), "project", MockPipeline(5), MockAzureRun(83))
63
+ logs = run.logs()
64
+ assert len(logs) == 2, logs
65
+
66
+
67
+ if __name__ == "__main__":
68
+ test_basic()
@@ -287,6 +287,7 @@ def test_math() -> None:
287
287
  now2 = now1 + timedelta(days=7)
288
288
  assert (now2 - now1).days == 7
289
289
  assert now2 - timedelta(days=7) == now1
290
+ assert not Timestamp.is_timestamp(5)
290
291
 
291
292
  try:
292
293
  assert now1 - 5 is False
@@ -6,7 +6,7 @@ from datetime import date, datetime
6
6
 
7
7
  from devopsdriver.azdo import Wiql
8
8
  from devopsdriver.azdo import Ascending, Descending, Value
9
- from devopsdriver.azdo import IsEmpty, IsNotEmpty, And, Or
9
+ from devopsdriver.azdo import IsEmpty, IsNotEmpty, And, Or, In, NotIn
10
10
  from devopsdriver.azdo import GreaterThan, LessThan, Equal, NotEqual
11
11
  from devopsdriver.azdo import GreaterThanOrEqual, LessThanOrEqual
12
12
 
@@ -65,7 +65,21 @@ def test_invalid_value_type() -> None:
65
65
  pass
66
66
 
67
67
 
68
+ def test_in_and_not_in() -> None:
69
+ """Test in and not in operators"""
70
+ builder = Wiql().where(
71
+ And(In("State", "New", "Ready for Development"), NotIn("Priority", 1, 2))
72
+ )
73
+ expected = (
74
+ """SELECT [System.Id] FROM workitems WHERE [System.State] """
75
+ + """IN ("New", "Ready for Development") AND [Microsoft.VSTS.Common.Priority] """
76
+ + """NOT IN (1, 2)"""
77
+ )
78
+ assert str(builder) == expected, str(builder)
79
+
80
+
68
81
  if __name__ == "__main__":
82
+ test_in_and_not_in()
69
83
  test_invalid_value_type()
70
84
  test_expressions()
71
85
  test_no_params()
@@ -0,0 +1,29 @@
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()
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- """ An Azure Devops WorkItem """
5
-
6
-
7
- from typing import Any
8
-
9
- from azure.devops.v7_1.work_item_tracking.models import WorkItem as AzureWorkItem
10
-
11
- from devopsdriver.azdo.timestamp import Timestamp
12
-
13
-
14
- class WorkItem: # pylint: disable=too-few-public-methods
15
- """Azure WorkItem"""
16
-
17
- def __init__(self, work_item: AzureWorkItem):
18
- self.raw = work_item
19
-
20
- @staticmethod
21
- def __matches_field(name: str, field: str) -> bool:
22
- name = name.lower()
23
- field = field.lower()
24
-
25
- if name == field:
26
- return True
27
-
28
- if name == field.replace(".", "_"):
29
- return True
30
-
31
- if name == field.split(".")[-1]:
32
- return True
33
-
34
- return False
35
-
36
- @staticmethod
37
- def _parse_field(name: str, data: dict) -> any:
38
- assert name and data
39
- found = [f for f in data if WorkItem.__matches_field(name, f)]
40
-
41
- if len(found) == 1:
42
- return (
43
- WorkItem._Dict(data[found[0]])
44
- if isinstance(data[found[0]], dict)
45
- else data[found[0]]
46
- )
47
-
48
- if "fields" in data:
49
- return WorkItem._parse_field(name, data["fields"])
50
-
51
- return None
52
-
53
- class _Dict(dict):
54
- def __getattr__(self, name: str) -> Any:
55
- value = WorkItem._parse_field(name, self)
56
-
57
- if Timestamp.is_timestamp(value):
58
- return Timestamp(value)
59
-
60
- return value
61
-
62
- def __getattr__(self, name: str) -> Any:
63
- value = WorkItem._parse_field(name, self.raw.as_dict())
64
-
65
- if Timestamp.is_timestamp(value):
66
- return Timestamp(value)
67
-
68
- return value
File without changes
File without changes