devopsdriver 0.1.37__tar.gz → 0.1.39__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 (35) hide show
  1. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/PKG-INFO +27 -8
  2. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/README.md +19 -7
  3. devopsdriver-0.1.39/devopsdriver/__init__.py +5 -0
  4. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver/azdo/__init__.py +2 -1
  5. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver/azdo/clients.py +3 -0
  6. devopsdriver-0.1.39/devopsdriver/azdo/timestamp.py +80 -0
  7. devopsdriver-0.1.39/devopsdriver/azdo/workitem/__init__.py +1 -0
  8. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver/azdo/workitem/client.py +3 -1
  9. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver/azdo/workitem/wiql.py +1 -0
  10. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver/azdo/workitem/workitem.py +18 -3
  11. devopsdriver-0.1.39/devopsdriver/manage_settings.py +55 -0
  12. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver/sendmail.py +4 -0
  13. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver/settings.py +3 -36
  14. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver/template.py +2 -0
  15. devopsdriver-0.1.39/devopsdriver/templates/manage_settings.txt.mako +14 -0
  16. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver.egg-info/PKG-INFO +27 -8
  17. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver.egg-info/SOURCES.txt +6 -0
  18. devopsdriver-0.1.39/devopsdriver.egg-info/entry_points.txt +2 -0
  19. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver.egg-info/requires.txt +10 -0
  20. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/pyproject.toml +17 -0
  21. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/tests/test_azure_clients.py +1 -1
  22. devopsdriver-0.1.39/tests/test_azure_timestamp.py +247 -0
  23. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/tests/test_azure_workitem.py +18 -9
  24. devopsdriver-0.1.39/tests/test_manage_settings.py +80 -0
  25. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/tests/test_sendmail.py +1 -1
  26. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/tests/test_settings.py +1 -44
  27. devopsdriver-0.1.37/devopsdriver/__init__.py +0 -10
  28. devopsdriver-0.1.37/devopsdriver/azdo/workitem/__init__.py +0 -4
  29. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/LICENSE +0 -0
  30. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver.egg-info/dependency_links.txt +0 -0
  31. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/devopsdriver.egg-info/top_level.txt +0 -0
  32. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/setup.cfg +0 -0
  33. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/tests/test_azure_workitem_client.py +0 -0
  34. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/tests/test_azure_workitem_wiql.py +0 -0
  35. {devopsdriver-0.1.37 → devopsdriver-0.1.39}/tests/test_template.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devopsdriver
3
- Version: 0.1.37
3
+ Version: 0.1.39
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,9 +51,16 @@ Requires-Dist: keyring==25.0.0
51
51
  Requires-Dist: setuptools==69.0.2
52
52
  Requires-Dist: azure-devops==7.1.0b4
53
53
  Requires-Dist: Mako==1.3.2
54
+ Provides-Extra: dev
55
+ Requires-Dist: black>=24.3.0; extra == "dev"
56
+ Requires-Dist: pylint>=3.1.0; extra == "dev"
57
+ Provides-Extra: test
58
+ Requires-Dist: pytest>=8.1.1; extra == "test"
59
+ Requires-Dist: coverage>=7.4.4; extra == "test"
60
+ Provides-Extra: doc
54
61
 
55
62
  ![status sheild](https://img.shields.io/static/v1?label=status&message=beta&color=blue&style=plastic)
56
- [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.37&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.37/)
63
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.39&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.39/)
57
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)
58
65
  [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
59
66
  [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
@@ -84,7 +91,16 @@ OS:
84
91
 
85
92
  Devops-driver is a collection of tools to help streamline developer's experience and gain insights into various processes.
86
93
 
87
- ## Access to secrets
94
+ ## Tools
95
+
96
+ devopsdriver is a toolbox that helps to quickly put together reports. Some of the items in the toolbox are:
97
+
98
+ - **Settings**: store data, constants, settings, keys, tokens, etc. both in and out of the repository
99
+ - **send_email**: send emails over SMTP (including SSL), using `Settings` to store credentials
100
+ - **Template**: Simplify generating reports using `.mako` templates
101
+ - **Azure.workitem**: Search for and inspect Azure DevOps work items
102
+
103
+ ## Example use-case
88
104
 
89
105
  To allow seamless work in both pipelines as well as in the development environment, the `Settings` object gives you access to common settings among multiple scripts, secrets, and configuration constants in a way the helps keep secrets out of the repository but runs just as well in the pipeline as your machine.
90
106
 
@@ -101,7 +117,6 @@ secrets:
101
117
  ```
102
118
 
103
119
  This file is in a global place (location varies by OS) and stores information that you may not want in your repository or is specific to development.
104
- The `secrets` are extra sensative and are stored in the OS keychain.
105
120
 
106
121
  | Platform | Global Directory |
107
122
  |----------|------------------------|
@@ -109,26 +124,28 @@ The `secrets` are extra sensative and are stored in the OS keychain.
109
124
  | Linux | ~/.devopsdriver/ |
110
125
  | macOS | ~/Library/Preferences/ |
111
126
 
127
+ The `secrets` are extra sensative and are stored in the OS keychain.
128
+
112
129
  ### Set secrets in the keychain
113
130
  ```bash
114
131
  $ python3 -m venv .venv
115
132
  $ source .venv/bin/activate
116
133
  $ pip install devopsdriver
117
- $ python -m devopsdriver.settings --secrets
134
+ $ settings --secrets
118
135
  secret: smtp.password key: smtp/password
119
136
  smtp.password (smtp/password): ****
120
137
  secret: azure.token key: azure/token
121
138
  smtp.password (azure/token): ****
122
- $ python -m devopsdriver.settings --secrets
139
+ $ settings --secrets
123
140
  secret: azure.token key: azure/token
124
141
  Value set
125
142
  secret: smtp.password key: smtp/password
126
143
  Value set
127
144
  $
128
145
  ```
129
- The first call to `devopsdriver.settings` will look for every secret and check if they are already set in the keychain.
146
+ The first call to `settings` will look for every secret and check if they are already set in the keychain.
130
147
  For any secret that has not been set in the keychain, you will be prompted to enter the password to store.
131
- The second call to `devopsdriver.settings` will verify that all the values have been set in the keychain.
148
+ The second call to `settings` will verify that all the values have been set in the keychain.
132
149
 
133
150
  ### devopsdriver.yml
134
151
  ```yaml
@@ -174,6 +191,8 @@ These are values that you want to use in your script but have them here for easy
174
191
  </ul>
175
192
  ```
176
193
 
194
+ This file is the template for the email body.
195
+
177
196
  ### new_stories.py
178
197
  ```python
179
198
  from datetime import date, timedelta
@@ -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.37&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.37/)
2
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.39&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.39/)
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)
@@ -30,7 +30,16 @@ OS:
30
30
 
31
31
  Devops-driver is a collection of tools to help streamline developer's experience and gain insights into various processes.
32
32
 
33
- ## Access to secrets
33
+ ## Tools
34
+
35
+ devopsdriver is a toolbox that helps to quickly put together reports. Some of the items in the toolbox are:
36
+
37
+ - **Settings**: store data, constants, settings, keys, tokens, etc. both in and out of the repository
38
+ - **send_email**: send emails over SMTP (including SSL), using `Settings` to store credentials
39
+ - **Template**: Simplify generating reports using `.mako` templates
40
+ - **Azure.workitem**: Search for and inspect Azure DevOps work items
41
+
42
+ ## Example use-case
34
43
 
35
44
  To allow seamless work in both pipelines as well as in the development environment, the `Settings` object gives you access to common settings among multiple scripts, secrets, and configuration constants in a way the helps keep secrets out of the repository but runs just as well in the pipeline as your machine.
36
45
 
@@ -47,7 +56,6 @@ secrets:
47
56
  ```
48
57
 
49
58
  This file is in a global place (location varies by OS) and stores information that you may not want in your repository or is specific to development.
50
- The `secrets` are extra sensative and are stored in the OS keychain.
51
59
 
52
60
  | Platform | Global Directory |
53
61
  |----------|------------------------|
@@ -55,26 +63,28 @@ The `secrets` are extra sensative and are stored in the OS keychain.
55
63
  | Linux | ~/.devopsdriver/ |
56
64
  | macOS | ~/Library/Preferences/ |
57
65
 
66
+ The `secrets` are extra sensative and are stored in the OS keychain.
67
+
58
68
  ### Set secrets in the keychain
59
69
  ```bash
60
70
  $ python3 -m venv .venv
61
71
  $ source .venv/bin/activate
62
72
  $ pip install devopsdriver
63
- $ python -m devopsdriver.settings --secrets
73
+ $ settings --secrets
64
74
  secret: smtp.password key: smtp/password
65
75
  smtp.password (smtp/password): ****
66
76
  secret: azure.token key: azure/token
67
77
  smtp.password (azure/token): ****
68
- $ python -m devopsdriver.settings --secrets
78
+ $ settings --secrets
69
79
  secret: azure.token key: azure/token
70
80
  Value set
71
81
  secret: smtp.password key: smtp/password
72
82
  Value set
73
83
  $
74
84
  ```
75
- The first call to `devopsdriver.settings` will look for every secret and check if they are already set in the keychain.
85
+ The first call to `settings` will look for every secret and check if they are already set in the keychain.
76
86
  For any secret that has not been set in the keychain, you will be prompted to enter the password to store.
77
- The second call to `devopsdriver.settings` will verify that all the values have been set in the keychain.
87
+ The second call to `settings` will verify that all the values have been set in the keychain.
78
88
 
79
89
  ### devopsdriver.yml
80
90
  ```yaml
@@ -120,6 +130,8 @@ These are values that you want to use in your script but have them here for easy
120
130
  </ul>
121
131
  ```
122
132
 
133
+ This file is the template for the email body.
134
+
123
135
  ### new_stories.py
124
136
  ```python
125
137
  from datetime import date, timedelta
@@ -0,0 +1,5 @@
1
+ """ DevOps tools """
2
+
3
+ __version__ = "0.1.39"
4
+ __author__ = "Marc Page"
5
+ __credits__ = ""
@@ -2,8 +2,9 @@
2
2
 
3
3
  # re export symbols for easier use
4
4
  from .clients import Azure
5
+ from .timestamp import Timestamp
5
6
 
6
- from .workitem import WorkItem
7
+ from .workitem.workitem import WorkItem
7
8
  from .workitem.wiql import Wiql, Value, Field
8
9
  from .workitem.wiql import Ascending, Descending, And, Or
9
10
  from .workitem.wiql import Equal, NotEqual, LessThanOrEqual, GreaterThanOrEqual
@@ -7,12 +7,15 @@ API Documented here:
7
7
  https://github.com/microsoft/azure-devops-python-api
8
8
  """
9
9
 
10
+
10
11
  from azure.devops.connection import Connection as AzureConnection
11
12
  from msrest.authentication import BasicAuthentication as MSBasicAuthentication
12
13
 
13
14
  from devopsdriver.settings import Settings
14
15
  from devopsdriver.azdo.workitem.client import Client as WIClient
15
16
 
17
+
18
+ # for testing
16
19
  CONNECTION = AzureConnection
17
20
  AUTHENTICATION = MSBasicAuthentication
18
21
 
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """ Tools that help when working with Azure """
5
+
6
+
7
+ from datetime import datetime, timezone
8
+
9
+
10
+ class Timestamp:
11
+ """An Azure timestamp"""
12
+
13
+ DATE_FORMAT = "%Y-%m-%dT%H:%M:%S"
14
+ US_PER_MS = 1000 # microseconds per millisecond
15
+ US_PER_SEC = 1000 * US_PER_MS # microseconds per second
16
+
17
+ @staticmethod
18
+ def is_timestamp(value: any) -> bool:
19
+ """Determines if the value is a timestamp string
20
+
21
+ Args:
22
+ value (any): The value to check
23
+
24
+ Returns:
25
+ bool: True if the value is an Azure timestamp
26
+ """
27
+ if not isinstance(value, str):
28
+ return False
29
+
30
+ if not value.endswith("Z"):
31
+ return False
32
+
33
+ try:
34
+ Timestamp.__parse_string(value)
35
+ return True
36
+
37
+ except ValueError:
38
+ return False
39
+
40
+ def __init__(self, value: datetime | str | float | int):
41
+ if isinstance(value, datetime):
42
+ self.value = value
43
+
44
+ elif isinstance(value, str):
45
+ self.value = Timestamp.__parse_string(value)
46
+
47
+ elif isinstance(value, (int, float)):
48
+ self.value = datetime.fromtimestamp(value, tz=timezone.utc)
49
+
50
+ def __str__(self) -> str:
51
+ return self.to_string()
52
+
53
+ def to_string(self) -> str:
54
+ """Returns the Azure formatted timestamp
55
+
56
+ Returns:
57
+ str: The correctly formatted string
58
+ """
59
+ milliseconds = f"{self.value.microsecond / Timestamp.US_PER_MS:03.0f}".rstrip(
60
+ "0"
61
+ )
62
+ return f"{self.value.strftime(Timestamp.DATE_FORMAT)}.{milliseconds}Z"
63
+
64
+ def to_timestamp(self) -> float:
65
+ """Converts to a number to use with time.time()
66
+
67
+ Returns:
68
+ float: The number of seconds since the epoch
69
+ """
70
+ return datetime.timestamp(self.value)
71
+
72
+ @staticmethod
73
+ def __parse_string(timestamp: str) -> datetime:
74
+ assert timestamp.endswith("Z"), timestamp
75
+ whole, fractional = timestamp.rsplit(".", 1)
76
+ result = datetime.strptime(whole, Timestamp.DATE_FORMAT)
77
+ fractional_seconds = float(f"0.{fractional[:-1].ljust(3, '0')}")
78
+ return result.replace(
79
+ microsecond=int(fractional_seconds * Timestamp.US_PER_SEC)
80
+ ).replace(tzinfo=timezone.utc)
@@ -0,0 +1 @@
1
+ """ init workitem module """
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+
3
4
  """ Azure WorkItem Client """
4
5
 
6
+
5
7
  from azure.devops.v7_1.work_item_tracking.models import Wiql as AzureWiql
6
8
  from azure.devops.v7_1.work_item_tracking.models import WorkItem as AzureWorkItem
7
9
  from azure.devops.v7_1.work_item_tracking.models import TeamContext
8
10
  from azure.devops.v7_1.work_item_tracking.models import WorkItemQueryResult
9
- from devopsdriver.azdo.workitem import WorkItem
11
+ from devopsdriver.azdo.workitem.workitem import WorkItem
10
12
  from devopsdriver.azdo.workitem.wiql import Wiql
11
13
 
12
14
 
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+
3
4
  """ Builds a WIQL query
4
5
  https://learn.microsoft.com/en-us/azure/devops/boards/queries/wiql-syntax?view=azure-devops
5
6
 
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+
3
4
  """ An Azure Devops WorkItem """
4
5
 
6
+
5
7
  from typing import Any
8
+
6
9
  from azure.devops.v7_1.work_item_tracking.models import WorkItem as AzureWorkItem
7
10
 
11
+ from devopsdriver.azdo.timestamp import Timestamp
12
+
8
13
 
9
14
  class WorkItem: # pylint: disable=too-few-public-methods
10
15
  """Azure WorkItem"""
@@ -43,11 +48,21 @@ class WorkItem: # pylint: disable=too-few-public-methods
43
48
  if "fields" in data:
44
49
  return WorkItem._parse_field(name, data["fields"])
45
50
 
46
- raise AttributeError(f"'WorkItem' object has no attribute '{name}'")
51
+ return None
47
52
 
48
53
  class _Dict(dict):
49
54
  def __getattr__(self, name: str) -> Any:
50
- return WorkItem._parse_field(name, self)
55
+ value = WorkItem._parse_field(name, self)
56
+
57
+ if Timestamp.is_timestamp(value):
58
+ return Timestamp(value)
59
+
60
+ return value
51
61
 
52
62
  def __getattr__(self, name: str) -> Any:
53
- return WorkItem._parse_field(name, self.raw.as_dict())
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
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """ Module Doc """
5
+
6
+
7
+ from os.path import dirname, join
8
+ from sys import argv as sys_argv
9
+ from getpass import getpass as os_getpass
10
+
11
+ from keyring import set_password
12
+
13
+ from .settings import Settings
14
+ from .template import Template
15
+
16
+ ARGV = sys_argv
17
+ PRINT = print
18
+ SET_PASSWORD = set_password
19
+ GET_PASS = os_getpass
20
+
21
+
22
+ def main() -> None:
23
+ """Get settings values"""
24
+ args = list(ARGV[1:])
25
+
26
+ if not args or "--help" in args or "-h" in args:
27
+ PRINT(
28
+ Template(
29
+ __file__, join(dirname(__file__), "templates"), extension=".txt.mako"
30
+ ).render()
31
+ )
32
+ return
33
+
34
+ settings = Settings(__file__, dirname(dirname(__file__))).key("secrets")
35
+
36
+ if "--secrets" in args:
37
+ args.remove("--secrets")
38
+
39
+ for secret, key in settings.secrets.items():
40
+ PRINT(f"secret: {secret} key: {key}")
41
+
42
+ if not settings.has(secret):
43
+ value = GET_PASS(f"{secret} ({key}): ")
44
+
45
+ if value:
46
+ SET_PASSWORD(*Settings.split_key(key), value)
47
+ else:
48
+ PRINT("\tValue set")
49
+
50
+ for arg in args:
51
+ PRINT(f"{settings.get(arg)}")
52
+
53
+
54
+ if __name__ == "__main__":
55
+ main()
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+
3
4
  """ Ability to send emails with embedded images """
5
+
6
+
4
7
  from smtplib import SMTP as OS_SMTP, SMTP_SSL as OS_SMTP_SSL
5
8
  from email.mime.multipart import MIMEMultipart as OS_MIMEMultipart
6
9
  from email.mime.text import MIMEText as OS_MIMEText
@@ -8,6 +11,7 @@ from email.mime.image import MIMEImage as OS_MIMEImage
8
11
 
9
12
  from devopsdriver.settings import Settings
10
13
 
14
+
11
15
  IMAGE_HEADERS = {".png": b"\x89PNG\r\n\x1a\n", ".jpg": b"\xff\xd8\xff"}
12
16
 
13
17
  # for testing
@@ -86,6 +86,8 @@ from getpass import getpass as os_getpass
86
86
 
87
87
  from yaml import safe_load
88
88
  from keyring import get_password, set_password
89
+ from keyring.backends import fail
90
+
89
91
 
90
92
  # for testing
91
93
  ENVIRON = os_environ
@@ -266,7 +268,7 @@ class Settings:
266
268
  if key in self.secrets:
267
269
  value = GET_PASSWORD(*Settings.split_key(self.secrets[key]))
268
270
 
269
- if value is not None:
271
+ if value is not None and not isinstance(value, fail.Keyring):
270
272
  return True if check else value
271
273
 
272
274
  # Last check the files for settings
@@ -357,38 +359,3 @@ class Settings:
357
359
  Settings.__merge(settings, contents)
358
360
 
359
361
  return settings
360
-
361
-
362
- def main() -> None:
363
- """Get settings values"""
364
- args = list(ARGV[1:])
365
-
366
- if not args or "--help" in args or "-h" in args:
367
- PRINT("pass in settings to see if they are set and to what value")
368
- PRINT("You can pass dotted names to get inner values, like smpt.server")
369
- PRINT("You can pass --secrets to set keychain values that have not been set")
370
- PRINT("You can also pass --help or -h to get this message")
371
- return
372
-
373
- settings = Settings(__file__, dirname(dirname(__file__))).key("secrets")
374
-
375
- if "--secrets" in args:
376
- args.remove("--secrets")
377
-
378
- for secret, key in settings.secrets.items():
379
- PRINT(f"secret: {secret} key: {key}")
380
-
381
- if not settings.has(secret):
382
- value = GET_PASS(f"{secret} ({key}): ")
383
-
384
- if value:
385
- SET_PASSWORD(*Settings.split_key(key), value)
386
- else:
387
- PRINT("\tValue set")
388
-
389
- for arg in args:
390
- PRINT(f"{settings.get(arg)}")
391
-
392
-
393
- if __name__ == "__main__":
394
- main()
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+
3
4
  """ Module Doc """
4
5
 
6
+
5
7
  from os.path import dirname, basename, splitext
6
8
 
7
9
  from mako.lookup import TemplateLookup
@@ -0,0 +1,14 @@
1
+ Usage:
2
+
3
+ settings [-h | --help]
4
+ settings --secrets
5
+ settings <value>...
6
+
7
+ -h --help Display this help information
8
+
9
+ --secrets Prompt for any secrets that have not been set in the keychain
10
+ For secrets that are set, confirms that the value is set
11
+
12
+ <value> A value to display the value for. For instance:
13
+ "smtp" will display all the smtp values (but not secrets in smtp, like password)
14
+ "smtp.server" will display just the smtp server
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devopsdriver
3
- Version: 0.1.37
3
+ Version: 0.1.39
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,9 +51,16 @@ Requires-Dist: keyring==25.0.0
51
51
  Requires-Dist: setuptools==69.0.2
52
52
  Requires-Dist: azure-devops==7.1.0b4
53
53
  Requires-Dist: Mako==1.3.2
54
+ Provides-Extra: dev
55
+ Requires-Dist: black>=24.3.0; extra == "dev"
56
+ Requires-Dist: pylint>=3.1.0; extra == "dev"
57
+ Provides-Extra: test
58
+ Requires-Dist: pytest>=8.1.1; extra == "test"
59
+ Requires-Dist: coverage>=7.4.4; extra == "test"
60
+ Provides-Extra: doc
54
61
 
55
62
  ![status sheild](https://img.shields.io/static/v1?label=status&message=beta&color=blue&style=plastic)
56
- [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.37&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.37/)
63
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.39&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.39/)
57
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)
58
65
  [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
59
66
  [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
@@ -84,7 +91,16 @@ OS:
84
91
 
85
92
  Devops-driver is a collection of tools to help streamline developer's experience and gain insights into various processes.
86
93
 
87
- ## Access to secrets
94
+ ## Tools
95
+
96
+ devopsdriver is a toolbox that helps to quickly put together reports. Some of the items in the toolbox are:
97
+
98
+ - **Settings**: store data, constants, settings, keys, tokens, etc. both in and out of the repository
99
+ - **send_email**: send emails over SMTP (including SSL), using `Settings` to store credentials
100
+ - **Template**: Simplify generating reports using `.mako` templates
101
+ - **Azure.workitem**: Search for and inspect Azure DevOps work items
102
+
103
+ ## Example use-case
88
104
 
89
105
  To allow seamless work in both pipelines as well as in the development environment, the `Settings` object gives you access to common settings among multiple scripts, secrets, and configuration constants in a way the helps keep secrets out of the repository but runs just as well in the pipeline as your machine.
90
106
 
@@ -101,7 +117,6 @@ secrets:
101
117
  ```
102
118
 
103
119
  This file is in a global place (location varies by OS) and stores information that you may not want in your repository or is specific to development.
104
- The `secrets` are extra sensative and are stored in the OS keychain.
105
120
 
106
121
  | Platform | Global Directory |
107
122
  |----------|------------------------|
@@ -109,26 +124,28 @@ The `secrets` are extra sensative and are stored in the OS keychain.
109
124
  | Linux | ~/.devopsdriver/ |
110
125
  | macOS | ~/Library/Preferences/ |
111
126
 
127
+ The `secrets` are extra sensative and are stored in the OS keychain.
128
+
112
129
  ### Set secrets in the keychain
113
130
  ```bash
114
131
  $ python3 -m venv .venv
115
132
  $ source .venv/bin/activate
116
133
  $ pip install devopsdriver
117
- $ python -m devopsdriver.settings --secrets
134
+ $ settings --secrets
118
135
  secret: smtp.password key: smtp/password
119
136
  smtp.password (smtp/password): ****
120
137
  secret: azure.token key: azure/token
121
138
  smtp.password (azure/token): ****
122
- $ python -m devopsdriver.settings --secrets
139
+ $ settings --secrets
123
140
  secret: azure.token key: azure/token
124
141
  Value set
125
142
  secret: smtp.password key: smtp/password
126
143
  Value set
127
144
  $
128
145
  ```
129
- The first call to `devopsdriver.settings` will look for every secret and check if they are already set in the keychain.
146
+ The first call to `settings` will look for every secret and check if they are already set in the keychain.
130
147
  For any secret that has not been set in the keychain, you will be prompted to enter the password to store.
131
- The second call to `devopsdriver.settings` will verify that all the values have been set in the keychain.
148
+ The second call to `settings` will verify that all the values have been set in the keychain.
132
149
 
133
150
  ### devopsdriver.yml
134
151
  ```yaml
@@ -174,6 +191,8 @@ These are values that you want to use in your script but have them here for easy
174
191
  </ul>
175
192
  ```
176
193
 
194
+ This file is the template for the email body.
195
+
177
196
  ### new_stories.py
178
197
  ```python
179
198
  from datetime import date, timedelta
@@ -2,24 +2,30 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  devopsdriver/__init__.py
5
+ devopsdriver/manage_settings.py
5
6
  devopsdriver/sendmail.py
6
7
  devopsdriver/settings.py
7
8
  devopsdriver/template.py
8
9
  devopsdriver.egg-info/PKG-INFO
9
10
  devopsdriver.egg-info/SOURCES.txt
10
11
  devopsdriver.egg-info/dependency_links.txt
12
+ devopsdriver.egg-info/entry_points.txt
11
13
  devopsdriver.egg-info/requires.txt
12
14
  devopsdriver.egg-info/top_level.txt
13
15
  devopsdriver/azdo/__init__.py
14
16
  devopsdriver/azdo/clients.py
17
+ devopsdriver/azdo/timestamp.py
15
18
  devopsdriver/azdo/workitem/__init__.py
16
19
  devopsdriver/azdo/workitem/client.py
17
20
  devopsdriver/azdo/workitem/wiql.py
18
21
  devopsdriver/azdo/workitem/workitem.py
22
+ devopsdriver/templates/manage_settings.txt.mako
19
23
  tests/test_azure_clients.py
24
+ tests/test_azure_timestamp.py
20
25
  tests/test_azure_workitem.py
21
26
  tests/test_azure_workitem_client.py
22
27
  tests/test_azure_workitem_wiql.py
28
+ tests/test_manage_settings.py
23
29
  tests/test_sendmail.py
24
30
  tests/test_settings.py
25
31
  tests/test_template.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ settings = devopsdriver.manage_settings:main
@@ -3,3 +3,13 @@ keyring==25.0.0
3
3
  setuptools==69.0.2
4
4
  azure-devops==7.1.0b4
5
5
  Mako==1.3.2
6
+
7
+ [dev]
8
+ black>=24.3.0
9
+ pylint>=3.1.0
10
+
11
+ [doc]
12
+
13
+ [test]
14
+ pytest>=8.1.1
15
+ coverage>=7.4.4
@@ -28,6 +28,23 @@ authors = [
28
28
  {name = "Marc Page", email = "marcallenpage@gmail.com"},
29
29
  ]
30
30
 
31
+ [project.scripts]
32
+ settings = "devopsdriver.manage_settings:main"
33
+
34
+ [tool.setuptools.package-data]
35
+ "*" = ["*.mako"]
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "black>=24.3.0",
40
+ "pylint>=3.1.0",
41
+ ]
42
+ test = [
43
+ "pytest>=8.1.1",
44
+ "coverage>=7.4.4",
45
+ ]
46
+ doc = []
47
+
31
48
  [project.urls]
32
49
  Homepage = "https://github.com/marcpage/devops-driver"
33
50
  Documentation = "https://github.com/marcpage/devops-driver"
@@ -10,7 +10,7 @@ from os.path import join
10
10
 
11
11
  from helpers import setup_settings, write
12
12
 
13
- from devopsdriver import Azure
13
+ from devopsdriver.azdo import Azure
14
14
  from devopsdriver.azdo import clients
15
15
 
16
16
 
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """ Test Azure Timestamp """
4
+
5
+ from devopsdriver.azdo import Timestamp
6
+
7
+ TEST_TIMESTAMPS = [
8
+ "2023-11-16T03:24:40.36Z",
9
+ "2023-11-16T03:17:01.413Z",
10
+ "2023-11-16T05:09:11.62Z",
11
+ "2023-11-16T16:21:29.393Z",
12
+ "2023-11-16T16:21:48.88Z",
13
+ "2023-11-16T17:49:38.527Z",
14
+ "2023-11-16T22:47:07.257Z",
15
+ "2023-11-17T03:54:07.34Z",
16
+ "2023-11-18T22:31:38.557Z",
17
+ "2023-11-21T03:54:54.98Z",
18
+ "2023-11-21T17:12:58.44Z",
19
+ "2023-11-21T17:17:13.987Z",
20
+ "2023-11-21T17:35:06.58Z",
21
+ "2023-11-24T16:17:49.49Z",
22
+ "2023-11-24T16:18:09.48Z",
23
+ "2023-11-24T16:24:42.23Z",
24
+ "2023-11-24T19:00:29.217Z",
25
+ "2023-11-24T19:01:07.813Z",
26
+ "2023-11-29T03:18:24.333Z",
27
+ "2023-11-29T03:21:16.353Z",
28
+ "2023-11-29T03:21:20.96Z",
29
+ "2023-11-29T03:25:54.02Z",
30
+ "2023-11-29T03:28:59.403Z",
31
+ "2023-11-29T03:29:55.51Z",
32
+ "2023-11-29T03:44:19.653Z",
33
+ "2023-11-29T03:48:15.777Z",
34
+ "2023-11-29T15:49:26.967Z",
35
+ "2023-11-29T15:49:33.457Z",
36
+ "2023-11-29T15:54:59.17Z",
37
+ "2023-12-05T01:53:52.67Z",
38
+ "2023-12-05T01:54:03.417Z",
39
+ "2023-12-09T02:49:15.053Z",
40
+ "2023-12-09T02:53:44.95Z",
41
+ "2023-12-16T02:16:58.697Z",
42
+ "2024-01-15T15:10:41.83Z",
43
+ "2024-01-15T16:27:31.43Z",
44
+ "2024-01-15T19:16:11.38Z",
45
+ "2024-01-15T19:16:16.017Z",
46
+ "2024-01-16T21:21:48.23Z",
47
+ "2024-01-16T21:21:48.87Z",
48
+ "2024-01-16T21:21:54.127Z",
49
+ "2024-01-16T21:21:55.277Z",
50
+ "2024-01-16T21:21:57.223Z",
51
+ "2024-01-16T21:31:02.743Z",
52
+ "2024-02-01T15:12:12.277Z",
53
+ "2024-02-02T17:30:43.253Z",
54
+ "2024-02-02T17:31:18.313Z",
55
+ "2024-02-02T17:49:10.37Z",
56
+ "2024-02-02T17:51:32.537Z",
57
+ "2024-02-02T17:55:17.257Z",
58
+ "2024-02-05T03:13:24.16Z",
59
+ "2024-02-05T03:13:24.66Z",
60
+ "2024-02-05T03:15:24.9Z",
61
+ "2024-02-05T14:46:44.583Z",
62
+ "2024-02-05T14:47:54.803Z",
63
+ "2024-02-12T17:01:23.693Z",
64
+ "2024-02-12T17:04:20.5Z",
65
+ "2024-02-13T00:30:14.31Z",
66
+ "2024-02-13T00:55:01.35Z",
67
+ "2024-02-13T16:08:30.643Z",
68
+ "2024-02-13T16:46:34.79Z",
69
+ "2024-02-13T16:46:35.35Z",
70
+ "2024-02-15T20:41:26.767Z",
71
+ "2024-02-20T17:18:06.95Z",
72
+ "2024-02-20T17:18:07.49Z",
73
+ "2024-02-20T17:18:09.537Z",
74
+ "2024-02-21T15:11:34.86Z",
75
+ "2024-02-21T15:30:12.723Z",
76
+ "2024-02-21T15:30:28.887Z",
77
+ "2024-02-21T15:30:57.373Z",
78
+ "2024-02-21T15:36:03.137Z",
79
+ "2024-02-21T15:37:26.463Z",
80
+ "2024-02-21T16:53:53.29Z",
81
+ "2024-02-21T16:53:57.943Z",
82
+ "2024-02-21T17:55:39.323Z",
83
+ "2024-02-22T16:01:50.477Z",
84
+ "2024-02-22T16:02:19.69Z",
85
+ "2024-02-22T17:22:02.963Z",
86
+ "2024-02-22T17:22:05.7Z",
87
+ "2024-02-27T17:11:56.247Z",
88
+ "2024-03-03T21:11:41.707Z",
89
+ "2024-03-03T21:11:42.257Z",
90
+ "2024-03-03T21:17:57.317Z",
91
+ "2024-03-03T21:18:19.83Z",
92
+ "2024-03-03T21:19:16.68Z",
93
+ "2024-03-03T21:23:35.33Z",
94
+ "2024-03-08T20:26:21.927Z",
95
+ "2024-03-08T20:27:36.023Z",
96
+ "2024-03-08T21:46:49.617Z",
97
+ "2024-03-10T18:28:05.983Z",
98
+ "2024-03-10T20:18:28.353Z",
99
+ "2024-03-10T20:27:13.34Z",
100
+ "2024-03-10T20:28:19.893Z",
101
+ "2024-03-10T22:22:23.163Z",
102
+ "2024-03-11T00:00:10.72Z",
103
+ "2024-03-11T01:15:51.407Z",
104
+ "2024-03-11T01:15:57.817Z",
105
+ "2024-03-11T03:39:43.92Z",
106
+ "2024-03-11T15:46:50.733Z",
107
+ "2024-03-11T15:48:01.667Z",
108
+ "2024-03-11T15:58:51.747Z",
109
+ "2024-03-11T18:37:30.577Z",
110
+ "2024-03-11T18:50:51.663Z",
111
+ "2024-03-11T18:57:36.687Z",
112
+ "2024-03-11T19:14:03.663Z",
113
+ "2024-03-11T19:15:28.78Z",
114
+ "2024-03-11T22:52:00.85Z",
115
+ "2024-03-12T00:44:38.52Z",
116
+ "2024-03-12T00:45:25.517Z",
117
+ "2024-03-12T00:45:31.18Z",
118
+ "2024-03-12T00:46:11.873Z",
119
+ "2024-03-14T19:11:36.84Z",
120
+ "2024-03-14T21:21:55.313Z",
121
+ "2024-03-14T21:23:05.68Z",
122
+ "2024-03-14T21:23:06.013Z",
123
+ "2024-03-14T21:23:13.693Z",
124
+ "2024-03-14T21:24:06.643Z",
125
+ "2024-03-15T20:46:03.337Z",
126
+ "2024-03-15T20:46:07.973Z",
127
+ "2024-03-15T20:52:28.92Z",
128
+ "2024-03-15T21:01:55.407Z",
129
+ "2024-03-18T20:13:03.047Z",
130
+ "2024-03-19T02:46:45.227Z",
131
+ "2024-03-19T02:53:31.133Z",
132
+ "2024-03-19T02:58:25.87Z",
133
+ "2024-03-19T02:59:42.95Z",
134
+ "2024-03-19T03:00:17.017Z",
135
+ "2024-03-19T19:21:11.957Z",
136
+ "2024-03-19T21:13:49.46Z",
137
+ "2024-03-19T22:29:45.097Z",
138
+ "2024-03-20T14:52:04.92Z",
139
+ "2024-03-21T15:04:26.963Z",
140
+ "2024-03-21T15:04:59.003Z",
141
+ "2024-03-21T15:05:08.52Z",
142
+ "2024-03-21T15:26:09.337Z",
143
+ "2024-03-21T15:29:49.457Z",
144
+ "2024-03-21T15:30:33.85Z",
145
+ "2024-03-21T17:29:38.59Z",
146
+ "2024-03-21T18:07:55.047Z",
147
+ "2024-03-21T18:08:22.883Z",
148
+ "2024-03-22T17:23:07.13Z",
149
+ "2024-03-22T17:31:21.85Z",
150
+ "2024-03-22T21:55:56.293Z",
151
+ "2024-03-22T21:55:57.267Z",
152
+ "2024-03-23T16:04:49.38Z",
153
+ "2024-03-25T23:37:29.403Z",
154
+ "2024-03-25T23:37:45.44Z",
155
+ "2024-03-25T23:38:14.967Z",
156
+ "2024-03-26T00:01:26.593Z",
157
+ "2024-03-26T00:01:36.127Z",
158
+ "2024-03-26T15:55:04.947Z",
159
+ "2024-03-26T16:10:08.82Z",
160
+ "2024-03-26T16:24:48.657Z",
161
+ "2024-03-26T16:24:49.053Z",
162
+ "2024-03-26T16:24:53.573Z",
163
+ "2024-03-27T13:21:04.567Z",
164
+ "2024-03-27T13:32:11.08Z",
165
+ "2024-03-27T13:32:12.76Z",
166
+ "2024-03-27T13:33:08.953Z",
167
+ "2024-03-27T17:29:49.157Z",
168
+ "2024-03-27T17:37:58.327Z",
169
+ "2024-03-27T17:39:51.85Z",
170
+ "2024-03-27T17:40:09.95Z",
171
+ "2024-03-27T17:40:21.84Z",
172
+ "2024-03-28T15:15:34.357Z",
173
+ "2024-03-28T16:09:35.753Z",
174
+ "2024-03-28T16:27:05.56Z",
175
+ "2024-03-28T16:33:28.717Z",
176
+ "2024-03-28T16:33:35.71Z",
177
+ "2024-03-28T16:33:56.49Z",
178
+ "2024-03-28T16:35:01.127Z",
179
+ "2024-03-28T16:35:03.513Z",
180
+ "2024-03-28T16:35:11.627Z",
181
+ "2024-03-28T16:35:48.313Z",
182
+ "2024-03-28T16:36:11.28Z",
183
+ "2024-03-28T16:37:02.943Z",
184
+ "2024-03-28T16:37:30.85Z",
185
+ "2024-03-28T16:37:32.873Z",
186
+ "2024-03-28T16:42:45.65Z",
187
+ "2024-03-28T16:42:48.86Z",
188
+ "2024-03-28T16:42:51.15Z",
189
+ "2024-04-01T16:27:02.117Z",
190
+ "2024-04-02T01:31:12.047Z",
191
+ "2024-04-02T01:31:36.603Z",
192
+ "2024-04-02T01:34:48.52Z",
193
+ "2024-04-02T01:34:57.02Z",
194
+ "2024-04-02T01:35:44.64Z",
195
+ "2024-04-02T16:12:35.537Z",
196
+ "2024-04-02T16:12:44.213Z",
197
+ "2024-04-02T16:15:18.68Z",
198
+ "2024-04-02T16:21:59.76Z",
199
+ "2024-04-02T16:24:44.087Z",
200
+ "2024-04-02T16:24:52.65Z",
201
+ "2024-04-02T16:25:09.547Z",
202
+ "2024-04-02T16:25:55.57Z",
203
+ "2024-04-02T16:27:56.313Z",
204
+ "2024-04-02T16:28:00.403Z",
205
+ "2024-04-02T16:29:39.293Z",
206
+ "2024-04-02T16:32:20.54Z",
207
+ "2024-04-02T16:32:41.527Z",
208
+ "2024-04-02T16:42:20.413Z",
209
+ "2024-04-02T20:38:32.47Z",
210
+ "2024-04-02T20:39:42.337Z",
211
+ "2024-04-03T01:26:25.087Z",
212
+ "2024-04-04T16:04:50.917Z",
213
+ "2024-04-04T16:05:11.873Z",
214
+ "2024-04-04T16:05:17.573Z",
215
+ "2024-04-04T16:06:35.577Z",
216
+ "2024-04-04T16:07:22.947Z",
217
+ "2024-04-04T16:09:58.193Z",
218
+ "2024-04-04T16:10:06.467Z",
219
+ "2024-04-04T16:10:11.853Z",
220
+ "2024-04-04T16:10:28.763Z",
221
+ "2024-04-04T16:10:31.533Z",
222
+ "2024-04-04T16:15:19.473Z",
223
+ "2024-04-04T16:15:23.39Z",
224
+ "2024-04-04T16:16:09.86Z",
225
+ "2024-04-04T18:09:35.133Z",
226
+ "2024-04-04T23:12:30.663Z",
227
+ ]
228
+
229
+
230
+ def test_basic() -> None:
231
+ """Test basic timestamp functionality"""
232
+ for timestamp_string in TEST_TIMESTAMPS:
233
+ value_under_test = Timestamp(timestamp_string)
234
+ assert (
235
+ str(value_under_test) == timestamp_string
236
+ ), f"{str(value_under_test)} != {timestamp_string}"
237
+ timestamp = value_under_test.to_timestamp()
238
+ assert (
239
+ abs(timestamp - Timestamp(timestamp).to_timestamp()) < 0.001
240
+ ), f"{timestamp} != {Timestamp(timestamp).to_timestamp()}"
241
+ assert (
242
+ abs(timestamp - Timestamp(value_under_test.value).to_timestamp()) < 0.001
243
+ ), f"{timestamp} != {Timestamp(value_under_test.value).to_timestamp()}"
244
+
245
+
246
+ if __name__ == "__main__":
247
+ test_basic()
@@ -21,7 +21,7 @@ class MockAzureWorkItem:
21
21
  "System.Reason": "New",
22
22
  "System.CreatedDate": "2023-11-16T03:12:32.94Z",
23
23
  "System.CreatedBy": {
24
- "displayName": "Edna Johnson",
24
+ "displayName": "Edna Johnson Z",
25
25
  "url": "https://spsprodcus5.vssps.visualstudio.com/"
26
26
  + "A3eb27a26-75f2-40f9-87dc-cc10e8e565e4/_apis/Identities"
27
27
  + "/45fcf770-0670-69d4-8e48-3ae6e0bf9b5c",
@@ -40,7 +40,8 @@ class MockAzureWorkItem:
40
40
  },
41
41
  "System.ChangedDate": "2023-11-16T03:12:32.94Z",
42
42
  "System.ChangedBy": {
43
- "displayName": "Edna Johnson",
43
+ "displayName": "Edna Johnson .Z",
44
+ "changedOn": "2023-11-16T03:12:32.94Z",
44
45
  "url": "https://spsprodcus5.vssps.visualstudio.com/"
45
46
  + "A3eb27a26-75f2-40f9-87dc-cc10e8e565e4/_apis/Identities/"
46
47
  + "45fcf770-0670-69d4-8e48-3ae6e0bf9b5c",
@@ -60,7 +61,7 @@ class MockAzureWorkItem:
60
61
  "System.CommentCount": 0,
61
62
  "System.TeamProject": "Creative",
62
63
  "System.AreaPath": "Creative",
63
- "System.IterationPath": "Creative\\November 2 2023",
64
+ "System.IterationPath": "2023-11-16T03:12:32.alphaZ",
64
65
  "System.Title": "test",
65
66
  "Microsoft.VSTS.Common.Priority": 2,
66
67
  "Microsoft.VSTS.Common.ValueArea": "Business",
@@ -80,16 +81,24 @@ def test_workitem() -> None:
80
81
  assert wi.ID == 5, wi.ID
81
82
  assert wi.workitemtype == "User Story", wi.workitemtype
82
83
  assert wi.system_workitemtype == "User Story", wi.system_workitemtype
83
- assert wi.ChangedBy["displayName"] == "Edna Johnson", wi.changedBy
84
- assert wi.changedby.displayname == "Edna Johnson", wi.changedby.displayname
85
84
  assert wi.microsoft_vsts_common_priority == 2, wi.microsoft_vsts_common_priority
85
+ assert wi.not_a_field is None, wi.not_a_field
86
86
 
87
- try:
88
- _ = wi.not_a_field
89
87
 
90
- except AttributeError as error:
91
- assert "not_a_field" in str(error)
88
+ def test_timestamp() -> None:
89
+ """test timestamps"""
90
+ wi = WorkItem(MockAzureWorkItem())
91
+ assert wi.StateChangeDate.to_string() == "2023-11-16T03:12:32.94Z"
92
+ assert wi.CreatedDate.to_string() == "2023-11-16T03:12:32.94Z"
93
+ assert wi.ChangedDate.to_string() == "2023-11-16T03:12:32.94Z"
94
+ assert wi.IterationPath == "2023-11-16T03:12:32.alphaZ", wi.IterationPath
95
+ assert wi.ChangedBy["displayName"] == "Edna Johnson .Z", wi.changedBy["displayName"]
96
+ assert wi.createdBy.displayName == "Edna Johnson Z", wi.createdBy.displayName
97
+ assert (
98
+ wi.changedBy.changedOn.to_string() == "2023-11-16T03:12:32.94Z"
99
+ ), wi.changedBy.changedOn
92
100
 
93
101
 
94
102
  if __name__ == "__main__":
103
+ test_timestamp()
95
104
  test_workitem()
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """ Test the manage_settings tool """
4
+
5
+ from os.path import join
6
+ from tempfile import TemporaryDirectory
7
+ from helpers import setup_settings, write
8
+
9
+ from devopsdriver import settings
10
+ from devopsdriver import manage_settings
11
+
12
+
13
+ def test_main():
14
+ """test the main entry point"""
15
+ with TemporaryDirectory() as working_dir:
16
+ setup_settings(shared="test", Linux=join(working_dir, "Linux"))
17
+ settings.ARGV = ["ignore", "test"]
18
+ manage_settings.ARGV = ["ignore", "test"]
19
+ storage = {}
20
+
21
+ def mock_print(message: str) -> None:
22
+ storage["print"] = message
23
+
24
+ settings.PRINT = mock_print
25
+ manage_settings.PRINT = mock_print
26
+ write(join(working_dir, "Linux", "test.yml"), test=3)
27
+ manage_settings.main()
28
+ assert storage["print"] == "3", storage
29
+
30
+
31
+ def test_main_help():
32
+ """test the main entry point"""
33
+ with TemporaryDirectory() as working_dir:
34
+ setup_settings(shared="test", Linux=join(working_dir, "Linux"))
35
+ manage_settings.PRINT = settings.PRINT
36
+ settings.ARGV = ["ignore", "--help"]
37
+ manage_settings.ARGV = settings.ARGV
38
+ storage = {}
39
+
40
+ def mock_print(message: str) -> None:
41
+ storage["print"] = message
42
+
43
+ settings.PRINT = mock_print
44
+ manage_settings.PRINT = mock_print
45
+ write(join(working_dir, "Linux", "test.yml"), test=3)
46
+ manage_settings.main()
47
+ assert storage["print"].startswith("Usage:"), storage
48
+
49
+
50
+ def test_main_set_secret():
51
+ """test the main entry point when settings keychain secrets"""
52
+
53
+ def set_password(s, n, p):
54
+ assert (
55
+ s in ("azure", "jira") and n == "token" and p == "setec astronomy"
56
+ ), f"{s} {n} {p}"
57
+
58
+ with TemporaryDirectory() as working_dir:
59
+ setup_settings(shared="test", Linux=join(working_dir, "Linux"))
60
+ settings.ARGV = ["ignore", "--secrets"]
61
+ manage_settings.ARGV = settings.ARGV
62
+ settings.GET_PASSWORD = lambda s, n: (
63
+ "password" if f"{s}/{n}" == "azure/token" else None
64
+ )
65
+ settings.GET_PASS = lambda p: "setec astronomy"
66
+ manage_settings.GET_PASS = settings.GET_PASS
67
+ manage_settings.PRINT = settings.PRINT
68
+ settings.SET_PASSWORD = set_password
69
+ manage_settings.SET_PASSWORD = settings.SET_PASSWORD
70
+ write(
71
+ join(working_dir, "Linux", "test.yml"),
72
+ secrets={"azure.token": "azure/token", "jira.token": "jira/token"},
73
+ )
74
+ manage_settings.main()
75
+
76
+
77
+ if __name__ == "__main__":
78
+ test_main()
79
+ test_main_help()
80
+ test_main_set_secret()
@@ -3,7 +3,7 @@
3
3
  """ Test sendmail """
4
4
 
5
5
  from devopsdriver import sendmail # for debugging
6
- from devopsdriver import send_email
6
+ from devopsdriver.sendmail import send_email
7
7
 
8
8
 
9
9
  class MockMultipart(dict):
@@ -10,7 +10,7 @@ from itertools import product
10
10
  from helpers import setup_settings, ensure, write
11
11
 
12
12
  from devopsdriver import settings # debug access
13
- from devopsdriver import Settings
13
+ from devopsdriver.settings import Settings
14
14
 
15
15
 
16
16
  def __setup_files(directory: str, dir1: str, dir2: str) -> None:
@@ -238,24 +238,6 @@ def test_environ_values():
238
238
  assert opts["value"] == "testing ${noenv} for noenv", opts["value"]
239
239
 
240
240
 
241
- def test_main():
242
- """test the main entry point"""
243
- with TemporaryDirectory() as working_dir:
244
- setup_settings(shared="test", Linux=join(working_dir, "Linux"))
245
- settings.ARGV = ["ignore", "test"]
246
- write(join(working_dir, "Linux", "test.yml"), test=3)
247
- settings.main()
248
-
249
-
250
- def test_main_help():
251
- """test the main entry point"""
252
- with TemporaryDirectory() as working_dir:
253
- setup_settings(shared="test", Linux=join(working_dir, "Linux"))
254
- settings.ARGV = ["ignore", "--help"]
255
- write(join(working_dir, "Linux", "test.yml"), test=3)
256
- settings.main()
257
-
258
-
259
241
  def test_secret():
260
242
  """test os secret storage"""
261
243
  with TemporaryDirectory() as working_dir:
@@ -274,33 +256,8 @@ def test_secret():
274
256
  assert opts["password"] == "setec astronomy", opts["password"]
275
257
 
276
258
 
277
- def test_main_set_secret():
278
- """test the main entry point when settings keychain secrets"""
279
-
280
- def set_password(s, n, p):
281
- assert (
282
- s in ("azure", "jira") and n == "token" and p == "setec astronomy"
283
- ), f"{s} {n} {p}"
284
-
285
- with TemporaryDirectory() as working_dir:
286
- setup_settings(shared="test", Linux=join(working_dir, "Linux"))
287
- settings.ARGV = ["ignore", "--secrets"]
288
- settings.GET_PASSWORD = lambda s, n: (
289
- "password" if f"{s}/{n}" == "azure/token" else None
290
- )
291
- settings.GET_PASS = lambda p: "setec astronomy"
292
- settings.SET_PASSWORD = set_password
293
- write(
294
- join(working_dir, "Linux", "test.yml"),
295
- secrets={"azure.token": "azure/token", "jira.token": "jira/token"},
296
- )
297
- settings.main()
298
-
299
-
300
259
  if __name__ == "__main__":
301
- test_main_set_secret()
302
260
  test_secret()
303
- test_main()
304
261
  test_environ_values()
305
262
  test_cli_env_in_yaml()
306
263
  test_basic()
@@ -1,10 +0,0 @@
1
- """ DevOps tools """
2
-
3
- from .azdo import Azure
4
- from .settings import Settings
5
- from .sendmail import send_email
6
- from .template import Template
7
-
8
- __version__ = "0.1.37"
9
- __author__ = "Marc Page"
10
- __credits__ = ""
@@ -1,4 +0,0 @@
1
- """ init workitem module """
2
-
3
- # Re-export symbols to make things make sense
4
- from .workitem import WorkItem
File without changes
File without changes