devopsdriver 0.1.36__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 (39) hide show
  1. devopsdriver-0.1.39/PKG-INFO +249 -0
  2. devopsdriver-0.1.39/README.md +188 -0
  3. devopsdriver-0.1.39/devopsdriver/__init__.py +5 -0
  4. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/devopsdriver/azdo/__init__.py +2 -1
  5. {devopsdriver-0.1.36 → 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.36 → devopsdriver-0.1.39}/devopsdriver/azdo/workitem/client.py +3 -1
  9. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/devopsdriver/azdo/workitem/wiql.py +3 -2
  10. {devopsdriver-0.1.36 → 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.36 → devopsdriver-0.1.39}/devopsdriver/sendmail.py +4 -0
  13. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/devopsdriver/settings.py +3 -36
  14. devopsdriver-0.1.39/devopsdriver/template.py +42 -0
  15. devopsdriver-0.1.39/devopsdriver/templates/manage_settings.txt.mako +14 -0
  16. devopsdriver-0.1.39/devopsdriver.egg-info/PKG-INFO +249 -0
  17. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/devopsdriver.egg-info/SOURCES.txt +9 -1
  18. devopsdriver-0.1.39/devopsdriver.egg-info/entry_points.txt +2 -0
  19. devopsdriver-0.1.39/devopsdriver.egg-info/requires.txt +15 -0
  20. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/pyproject.toml +18 -0
  21. {devopsdriver-0.1.36 → 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.36 → devopsdriver-0.1.39}/tests/test_azure_workitem.py +18 -9
  24. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/tests/test_azure_workitem_wiql.py +3 -3
  25. devopsdriver-0.1.39/tests/test_manage_settings.py +80 -0
  26. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/tests/test_sendmail.py +1 -1
  27. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/tests/test_settings.py +1 -44
  28. devopsdriver-0.1.39/tests/test_template.py +29 -0
  29. devopsdriver-0.1.36/PKG-INFO +0 -76
  30. devopsdriver-0.1.36/README.md +0 -23
  31. devopsdriver-0.1.36/devopsdriver/__init__.py +0 -9
  32. devopsdriver-0.1.36/devopsdriver/azdo/workitem/__init__.py +0 -4
  33. devopsdriver-0.1.36/devopsdriver.egg-info/PKG-INFO +0 -76
  34. devopsdriver-0.1.36/devopsdriver.egg-info/requires.txt +0 -4
  35. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/LICENSE +0 -0
  36. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/devopsdriver.egg-info/dependency_links.txt +0 -0
  37. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/devopsdriver.egg-info/top_level.txt +0 -0
  38. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/setup.cfg +0 -0
  39. {devopsdriver-0.1.36 → devopsdriver-0.1.39}/tests/test_azure_workitem_client.py +0 -0
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.1
2
+ Name: devopsdriver
3
+ Version: 0.1.39
4
+ Summary: DevOps tools
5
+ Author-email: Marc Page <marcallenpage@gmail.com>
6
+ License: This is free and unencumbered software released into the public domain.
7
+
8
+ Anyone is free to copy, modify, publish, use, compile, sell, or
9
+ distribute this software, either in source code form or as a compiled
10
+ binary, for any purpose, commercial or non-commercial, and by any
11
+ means.
12
+
13
+ In jurisdictions that recognize copyright laws, the author or authors
14
+ of this software dedicate any and all copyright interest in the
15
+ software to the public domain. We make this dedication for the benefit
16
+ of the public at large and to the detriment of our heirs and
17
+ successors. We intend this dedication to be an overt act of
18
+ relinquishment in perpetuity of all present and future rights to this
19
+ software under copyright law.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
+ OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ For more information, please refer to <https://unlicense.org>
30
+
31
+ Project-URL: Homepage, https://github.com/marcpage/devops-driver
32
+ Project-URL: Documentation, https://github.com/marcpage/devops-driver
33
+ Project-URL: Repository, https://github.com/marcpage/devops-driver.git
34
+ Project-URL: Issues, https://github.com/marcpage/devops-driver/issues
35
+ Project-URL: Changelog, https://github.com/marcpage/devops-driver
36
+ Keywords: azure,devops,jira,confluence,email,pipelines,tools
37
+ Classifier: Development Status :: 1 - Planning
38
+ Classifier: Environment :: Console
39
+ Classifier: Intended Audience :: Developers
40
+ Classifier: Programming Language :: Python
41
+ Classifier: Programming Language :: Python :: 3
42
+ Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
43
+ Classifier: Programming Language :: Python :: 3.12
44
+ Classifier: Topic :: Utilities
45
+ Classifier: Topic :: Software Development
46
+ Requires-Python: >=3.10
47
+ Description-Content-Type: text/markdown
48
+ License-File: LICENSE
49
+ Requires-Dist: PyYAML==6.0.1
50
+ Requires-Dist: keyring==25.0.0
51
+ Requires-Dist: setuptools==69.0.2
52
+ Requires-Dist: azure-devops==7.1.0b4
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
61
+
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.39&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.39/)
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
+ [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
66
+ [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
67
+
68
+ [![commit sheild](https://img.shields.io/github/last-commit/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver/commits)
69
+ [![activity sheild](https://img.shields.io/github/commit-activity/m/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver/commits)
70
+ [![GitHub top language](https://img.shields.io/github/languages/top/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver)
71
+ [![size sheild](https://img.shields.io/github/languages/code-size/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver)
72
+
73
+ [![example workflow](https://github.com/marcpage/devops-driver/actions/workflows/pr.yml/badge.svg)](https://github.com/marcpage/devops-driver/actions/workflows/pr.yml)
74
+ [![status sheild](https://img.shields.io/static/v1?label=test+coverage&message=99%&color=active&style=plastic)](https://github.com/marcpage/devops-driver/blob/main/Makefile#L4)
75
+ [![issues sheild](https://img.shields.io/github/issues-raw/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver/issues)
76
+ [![GitHub pull requests](https://img.shields.io/github/issues-pr/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/pulls)
77
+
78
+ [![follow sheild](https://img.shields.io/github/followers/marcpage?label=Follow&style=social)](https://github.com/marcpage?tab=followers)
79
+ [![watch sheild](https://img.shields.io/github/watchers/marcpage/devops-driver?label=Watch&style=social)](https://github.com/marcpage/devops-driver/watchers)
80
+
81
+ [![Python](https://img.shields.io/static/v1?label=&message=Pure%20Python&color=ffde57&style=plastic&logo=python)](https://python.org/)
82
+ [![Azure](https://img.shields.io/static/v1?label=&message=Supports%20Microsoft%20Azure&color=blue&style=plastic&logo=microsoftazure)](https://azure.microsoft.com/)
83
+ [![Gmail](https://img.shields.io/static/v1?label=&message=Supports%20Google%20Gmail&color=white&style=plastic&logo=gmail)](https://gmail.com/)
84
+
85
+ OS:
86
+ [![Windows](https://img.shields.io/static/v1?label=&message=Windows&color=blue&style=plastic&logo=windows)](https://microsoft.com/)
87
+ [![macOS](https://img.shields.io/static/v1?label=&message=macOS&color=white&logoColor=black&style=plastic&logo=apple)](https://apple.com/)
88
+ [![Linux](https://img.shields.io/static/v1?label=&message=Linux&color=seashell&logoColor=black&style=plastic&logo=linux)](https://linux.org/)
89
+
90
+ # devops-driver
91
+
92
+ Devops-driver is a collection of tools to help streamline developer's experience and gain insights into various processes.
93
+
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
104
+
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.
106
+
107
+ Say you want a pipeline that looks for User Stories that are newer than 3 days and send out an email.
108
+
109
+ ### \<platform dependent path\>/devopsdriver.yml
110
+ ```yaml
111
+ smtp:
112
+ sender: JohnDoe@company.com
113
+
114
+ secrets:
115
+ azure.token: azure/token
116
+ smtp.password: smtp/password
117
+ ```
118
+
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.
120
+
121
+ | Platform | Global Directory |
122
+ |----------|------------------------|
123
+ | Windows | %APPDATA%\ |
124
+ | Linux | ~/.devopsdriver/ |
125
+ | macOS | ~/Library/Preferences/ |
126
+
127
+ The `secrets` are extra sensative and are stored in the OS keychain.
128
+
129
+ ### Set secrets in the keychain
130
+ ```bash
131
+ $ python3 -m venv .venv
132
+ $ source .venv/bin/activate
133
+ $ pip install devopsdriver
134
+ $ settings --secrets
135
+ secret: smtp.password key: smtp/password
136
+ smtp.password (smtp/password): ****
137
+ secret: azure.token key: azure/token
138
+ smtp.password (azure/token): ****
139
+ $ settings --secrets
140
+ secret: azure.token key: azure/token
141
+ Value set
142
+ secret: smtp.password key: smtp/password
143
+ Value set
144
+ $
145
+ ```
146
+ The first call to `settings` will look for every secret and check if they are already set in the keychain.
147
+ For any secret that has not been set in the keychain, you will be prompted to enter the password to store.
148
+ The second call to `settings` will verify that all the values have been set in the keychain.
149
+
150
+ ### devopsdriver.yml
151
+ ```yaml
152
+ azure:
153
+ url: https://dev.azure.com/MyCompany
154
+
155
+ smtp:
156
+ server: smtp.company.com
157
+ port: 465
158
+
159
+ cli:
160
+ azure.token: --azure-token
161
+ smtp.password: --smtp-password
162
+ smtp.sender: --smtp-sender
163
+
164
+ env:
165
+ azure.token: AZURE_TOKEN
166
+ smtp.sender: SMTP_SENDER
167
+ smtp.password: SMTP_PASSWORD
168
+ ```
169
+
170
+ This file lives next to your scripts in your repository.
171
+ These are settings that would be used by everyone, including the pipeline.
172
+ The `cli` and `env` map command line switches and environment variables to those keys.
173
+ This allows for many options for setting values depending on your needs.
174
+
175
+ ### new_stories.yml
176
+ ```yaml
177
+ scrum masters:
178
+ - JohnDoe@company.com
179
+ - JaneDoe@company.com
180
+ ```
181
+ This file is specific to your script and not shared.
182
+ These are values that you want to use in your script but have them here for easy adjustment.
183
+
184
+ ### new_stories.html.mako
185
+ ```html
186
+ <h1>Stories created in the last ${days} days</h1>
187
+ <ul>
188
+ % for story in stories:
189
+ <li>${story.id} ${story.title}</li>
190
+ % endfor
191
+ </ul>
192
+ ```
193
+
194
+ This file is the template for the email body.
195
+
196
+ ### new_stories.py
197
+ ```python
198
+ from datetime import date, timedelta
199
+
200
+ from devopsdriver import Settings, Azure, send_email, Template
201
+ from devopsdriver.azdo import Wiql, GreaterThan
202
+
203
+ # Parse all the settings from files, command line, environment, and keychain
204
+ settings = Settings(__file__).key("secrets").cli("cli").env("env")
205
+
206
+ # Create connection to Azure Devops
207
+ azure = Azure(settings)
208
+
209
+ # Get User Stories created in the last three days
210
+ three_days_ago = date.today() - timedelta(days=settings["days of recent stories"])
211
+ new_stories = azure.workitem.find(
212
+ Wiql().where(GreaterThan("CreatedDate", three_days_ago))
213
+ )
214
+
215
+ # Generate html body of the email
216
+ message = Template(__file__).render(
217
+ days=settings["days of recent stories"],
218
+ stories=new_stories,
219
+ )
220
+
221
+ # Send the email
222
+ send_email(
223
+ recipients=settings["scrum masters"],
224
+ subject=f'Stories created in the last {settings["days of recent stories"]} days',
225
+ html_body=message,
226
+ settings=settings,
227
+ )
228
+ ```
229
+
230
+ ### The email sent
231
+
232
+ **From**: JohnDoe@company.com
233
+
234
+ **To**: JohnDoe@company.com, JaneDoe@company.com
235
+
236
+ **Subject**: Stories created in the last 3 days
237
+
238
+ #### Stories created in the last 3 days
239
+
240
+ - 745 Needs a preprocessing step that makes it case insensitive
241
+ - 749 Create GitHub action to automate process
242
+ - 750 Create GitHub action to automate process
243
+ - 751 Test
244
+ - 752 Feedback Capture
245
+ - 753 draft doc history retrieval method
246
+ - 754 frontend - store to schema
247
+ - 755 Transfer job to production. Setup migrations to move to production
248
+ - 756 Query subscription status from App
249
+
@@ -0,0 +1,188 @@
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.39&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.39/)
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
+ [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
5
+ [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
6
+
7
+ [![commit sheild](https://img.shields.io/github/last-commit/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver/commits)
8
+ [![activity sheild](https://img.shields.io/github/commit-activity/m/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver/commits)
9
+ [![GitHub top language](https://img.shields.io/github/languages/top/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver)
10
+ [![size sheild](https://img.shields.io/github/languages/code-size/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver)
11
+
12
+ [![example workflow](https://github.com/marcpage/devops-driver/actions/workflows/pr.yml/badge.svg)](https://github.com/marcpage/devops-driver/actions/workflows/pr.yml)
13
+ [![status sheild](https://img.shields.io/static/v1?label=test+coverage&message=99%&color=active&style=plastic)](https://github.com/marcpage/devops-driver/blob/main/Makefile#L4)
14
+ [![issues sheild](https://img.shields.io/github/issues-raw/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver/issues)
15
+ [![GitHub pull requests](https://img.shields.io/github/issues-pr/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/pulls)
16
+
17
+ [![follow sheild](https://img.shields.io/github/followers/marcpage?label=Follow&style=social)](https://github.com/marcpage?tab=followers)
18
+ [![watch sheild](https://img.shields.io/github/watchers/marcpage/devops-driver?label=Watch&style=social)](https://github.com/marcpage/devops-driver/watchers)
19
+
20
+ [![Python](https://img.shields.io/static/v1?label=&message=Pure%20Python&color=ffde57&style=plastic&logo=python)](https://python.org/)
21
+ [![Azure](https://img.shields.io/static/v1?label=&message=Supports%20Microsoft%20Azure&color=blue&style=plastic&logo=microsoftazure)](https://azure.microsoft.com/)
22
+ [![Gmail](https://img.shields.io/static/v1?label=&message=Supports%20Google%20Gmail&color=white&style=plastic&logo=gmail)](https://gmail.com/)
23
+
24
+ OS:
25
+ [![Windows](https://img.shields.io/static/v1?label=&message=Windows&color=blue&style=plastic&logo=windows)](https://microsoft.com/)
26
+ [![macOS](https://img.shields.io/static/v1?label=&message=macOS&color=white&logoColor=black&style=plastic&logo=apple)](https://apple.com/)
27
+ [![Linux](https://img.shields.io/static/v1?label=&message=Linux&color=seashell&logoColor=black&style=plastic&logo=linux)](https://linux.org/)
28
+
29
+ # devops-driver
30
+
31
+ Devops-driver is a collection of tools to help streamline developer's experience and gain insights into various processes.
32
+
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
43
+
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.
45
+
46
+ Say you want a pipeline that looks for User Stories that are newer than 3 days and send out an email.
47
+
48
+ ### \<platform dependent path\>/devopsdriver.yml
49
+ ```yaml
50
+ smtp:
51
+ sender: JohnDoe@company.com
52
+
53
+ secrets:
54
+ azure.token: azure/token
55
+ smtp.password: smtp/password
56
+ ```
57
+
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.
59
+
60
+ | Platform | Global Directory |
61
+ |----------|------------------------|
62
+ | Windows | %APPDATA%\ |
63
+ | Linux | ~/.devopsdriver/ |
64
+ | macOS | ~/Library/Preferences/ |
65
+
66
+ The `secrets` are extra sensative and are stored in the OS keychain.
67
+
68
+ ### Set secrets in the keychain
69
+ ```bash
70
+ $ python3 -m venv .venv
71
+ $ source .venv/bin/activate
72
+ $ pip install devopsdriver
73
+ $ settings --secrets
74
+ secret: smtp.password key: smtp/password
75
+ smtp.password (smtp/password): ****
76
+ secret: azure.token key: azure/token
77
+ smtp.password (azure/token): ****
78
+ $ settings --secrets
79
+ secret: azure.token key: azure/token
80
+ Value set
81
+ secret: smtp.password key: smtp/password
82
+ Value set
83
+ $
84
+ ```
85
+ The first call to `settings` will look for every secret and check if they are already set in the keychain.
86
+ For any secret that has not been set in the keychain, you will be prompted to enter the password to store.
87
+ The second call to `settings` will verify that all the values have been set in the keychain.
88
+
89
+ ### devopsdriver.yml
90
+ ```yaml
91
+ azure:
92
+ url: https://dev.azure.com/MyCompany
93
+
94
+ smtp:
95
+ server: smtp.company.com
96
+ port: 465
97
+
98
+ cli:
99
+ azure.token: --azure-token
100
+ smtp.password: --smtp-password
101
+ smtp.sender: --smtp-sender
102
+
103
+ env:
104
+ azure.token: AZURE_TOKEN
105
+ smtp.sender: SMTP_SENDER
106
+ smtp.password: SMTP_PASSWORD
107
+ ```
108
+
109
+ This file lives next to your scripts in your repository.
110
+ These are settings that would be used by everyone, including the pipeline.
111
+ The `cli` and `env` map command line switches and environment variables to those keys.
112
+ This allows for many options for setting values depending on your needs.
113
+
114
+ ### new_stories.yml
115
+ ```yaml
116
+ scrum masters:
117
+ - JohnDoe@company.com
118
+ - JaneDoe@company.com
119
+ ```
120
+ This file is specific to your script and not shared.
121
+ These are values that you want to use in your script but have them here for easy adjustment.
122
+
123
+ ### new_stories.html.mako
124
+ ```html
125
+ <h1>Stories created in the last ${days} days</h1>
126
+ <ul>
127
+ % for story in stories:
128
+ <li>${story.id} ${story.title}</li>
129
+ % endfor
130
+ </ul>
131
+ ```
132
+
133
+ This file is the template for the email body.
134
+
135
+ ### new_stories.py
136
+ ```python
137
+ from datetime import date, timedelta
138
+
139
+ from devopsdriver import Settings, Azure, send_email, Template
140
+ from devopsdriver.azdo import Wiql, GreaterThan
141
+
142
+ # Parse all the settings from files, command line, environment, and keychain
143
+ settings = Settings(__file__).key("secrets").cli("cli").env("env")
144
+
145
+ # Create connection to Azure Devops
146
+ azure = Azure(settings)
147
+
148
+ # Get User Stories created in the last three days
149
+ three_days_ago = date.today() - timedelta(days=settings["days of recent stories"])
150
+ new_stories = azure.workitem.find(
151
+ Wiql().where(GreaterThan("CreatedDate", three_days_ago))
152
+ )
153
+
154
+ # Generate html body of the email
155
+ message = Template(__file__).render(
156
+ days=settings["days of recent stories"],
157
+ stories=new_stories,
158
+ )
159
+
160
+ # Send the email
161
+ send_email(
162
+ recipients=settings["scrum masters"],
163
+ subject=f'Stories created in the last {settings["days of recent stories"]} days',
164
+ html_body=message,
165
+ settings=settings,
166
+ )
167
+ ```
168
+
169
+ ### The email sent
170
+
171
+ **From**: JohnDoe@company.com
172
+
173
+ **To**: JohnDoe@company.com, JaneDoe@company.com
174
+
175
+ **Subject**: Stories created in the last 3 days
176
+
177
+ #### Stories created in the last 3 days
178
+
179
+ - 745 Needs a preprocessing step that makes it case insensitive
180
+ - 749 Create GitHub action to automate process
181
+ - 750 Create GitHub action to automate process
182
+ - 751 Test
183
+ - 752 Feedback Capture
184
+ - 753 draft doc history retrieval method
185
+ - 754 frontend - store to schema
186
+ - 755 Transfer job to production. Setup migrations to move to production
187
+ - 756 Query subscription status from App
188
+
@@ -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
 
@@ -87,10 +88,10 @@ class Value: # pylint: disable=too-few-public-methods
87
88
 
88
89
  def __str__(self) -> str:
89
90
  if isinstance(self.value, datetime):
90
- return self.value.strftime("%Y-%m-%d %H:%M:%S")
91
+ return f'"{self.value.strftime("%m/%d/%Y %H:%M:%S")}"'
91
92
 
92
93
  if isinstance(self.value, date):
93
- return self.value.strftime("%Y-%m-%d")
94
+ return f'"{self.value.strftime("%m/%d/%Y")}"'
94
95
 
95
96
  if isinstance(self.value, int):
96
97
  return str(self.value)
@@ -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