devopsdriver 0.1.35__tar.gz → 0.1.36__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 (26) hide show
  1. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/PKG-INFO +2 -2
  2. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/README.md +1 -1
  3. devopsdriver-0.1.36/devopsdriver/__init__.py +9 -0
  4. {devopsdriver-0.1.35/devopsdriver/azure → devopsdriver-0.1.36/devopsdriver/azdo}/clients.py +1 -1
  5. {devopsdriver-0.1.35/devopsdriver/azure → devopsdriver-0.1.36/devopsdriver/azdo}/workitem/client.py +2 -2
  6. devopsdriver-0.1.36/devopsdriver/sendmail.py +88 -0
  7. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/devopsdriver/settings.py +10 -3
  8. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/devopsdriver.egg-info/PKG-INFO +2 -2
  9. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/devopsdriver.egg-info/SOURCES.txt +8 -6
  10. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/tests/test_azure_clients.py +1 -1
  11. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/tests/test_azure_workitem.py +1 -1
  12. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/tests/test_azure_workitem_client.py +2 -2
  13. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/tests/test_azure_workitem_wiql.py +5 -5
  14. devopsdriver-0.1.36/tests/test_sendmail.py +151 -0
  15. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/tests/test_settings.py +18 -20
  16. devopsdriver-0.1.35/devopsdriver/__init__.py +0 -7
  17. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/LICENSE +0 -0
  18. {devopsdriver-0.1.35/devopsdriver/azure → devopsdriver-0.1.36/devopsdriver/azdo}/__init__.py +0 -0
  19. {devopsdriver-0.1.35/devopsdriver/azure → devopsdriver-0.1.36/devopsdriver/azdo}/workitem/__init__.py +0 -0
  20. {devopsdriver-0.1.35/devopsdriver/azure → devopsdriver-0.1.36/devopsdriver/azdo}/workitem/wiql.py +0 -0
  21. {devopsdriver-0.1.35/devopsdriver/azure → devopsdriver-0.1.36/devopsdriver/azdo}/workitem/workitem.py +0 -0
  22. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/devopsdriver.egg-info/dependency_links.txt +0 -0
  23. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/devopsdriver.egg-info/requires.txt +0 -0
  24. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/devopsdriver.egg-info/top_level.txt +0 -0
  25. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/pyproject.toml +0 -0
  26. {devopsdriver-0.1.35 → devopsdriver-0.1.36}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devopsdriver
3
- Version: 0.1.35
3
+ Version: 0.1.36
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.
@@ -52,7 +52,7 @@ Requires-Dist: setuptools==69.0.2
52
52
  Requires-Dist: azure-devops==7.1.0b4
53
53
 
54
54
  ![status sheild](https://img.shields.io/static/v1?label=status&message=starting...&color=inactive&style=plastic)
55
- [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.35&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.35/)
55
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.36&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.36/)
56
56
  [![GitHub](https://img.shields.io/github/license/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
57
57
  [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
58
58
  [![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=starting...&color=inactive&style=plastic)
2
- [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.35&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.35/)
2
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.36&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.36/)
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)
@@ -0,0 +1,9 @@
1
+ """ DevOps tools """
2
+
3
+ from .azdo import Azure
4
+ from .settings import Settings
5
+ from .sendmail import send_email
6
+
7
+ __version__ = "0.1.36"
8
+ __author__ = "Marc Page"
9
+ __credits__ = ""
@@ -11,7 +11,7 @@ from azure.devops.connection import Connection as AzureConnection
11
11
  from msrest.authentication import BasicAuthentication as MSBasicAuthentication
12
12
 
13
13
  from devopsdriver.settings import Settings
14
- from devopsdriver.azure.workitem.client import Client as WIClient
14
+ from devopsdriver.azdo.workitem.client import Client as WIClient
15
15
 
16
16
  CONNECTION = AzureConnection
17
17
  AUTHENTICATION = MSBasicAuthentication
@@ -6,8 +6,8 @@ from azure.devops.v7_1.work_item_tracking.models import Wiql as AzureWiql
6
6
  from azure.devops.v7_1.work_item_tracking.models import WorkItem as AzureWorkItem
7
7
  from azure.devops.v7_1.work_item_tracking.models import TeamContext
8
8
  from azure.devops.v7_1.work_item_tracking.models import WorkItemQueryResult
9
- from devopsdriver.azure.workitem import WorkItem
10
- from devopsdriver.azure.workitem.wiql import Wiql
9
+ from devopsdriver.azdo.workitem import WorkItem
10
+ from devopsdriver.azdo.workitem.wiql import Wiql
11
11
 
12
12
 
13
13
  class Client:
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """ Ability to send emails with embedded images """
4
+ from smtplib import SMTP as OS_SMTP, SMTP_SSL as OS_SMTP_SSL
5
+ from email.mime.multipart import MIMEMultipart as OS_MIMEMultipart
6
+ from email.mime.text import MIMEText as OS_MIMEText
7
+ from email.mime.image import MIMEImage as OS_MIMEImage
8
+
9
+ from devopsdriver.settings import Settings
10
+
11
+ IMAGE_HEADERS = {".png": b"\x89PNG\r\n\x1a\n", ".jpg": b"\xff\xd8\xff"}
12
+
13
+ # for testing
14
+ MIMEMULTIPART = OS_MIMEMultipart
15
+ MIMETEXT = OS_MIMEText
16
+ MIMEIMAGE = OS_MIMEImage
17
+ SMTP = OS_SMTP
18
+ SMTPSSL = OS_SMTP_SSL
19
+
20
+
21
+ def image_extension(data: bytes) -> str:
22
+ """Given image data, determine the file extension
23
+
24
+ Args:
25
+ data (bytes): The image binary data
26
+
27
+ Raises:
28
+ AttributeError: If the data type is not recognized
29
+
30
+ Returns:
31
+ str: The extension, like ".png"
32
+ """
33
+ for extension, header in IMAGE_HEADERS.items():
34
+ if data.startswith(header):
35
+ return extension
36
+
37
+ raise AttributeError("Image not a known format: " + ",".join(IMAGE_HEADERS))
38
+
39
+
40
+ def send_email(
41
+ recipients: str | list[str],
42
+ subject: str,
43
+ html_body: str,
44
+ settings: Settings = None,
45
+ **image_data,
46
+ ):
47
+ """Sends an email with embedded images
48
+
49
+ Args:
50
+ recipients (str | list[str]): A single email address or a list of them
51
+ subject (str): Subject line
52
+ html_body (str): html formatted body. To reference an image in your
53
+ body, <img src="cid:image1"> if you pass image1=png.read()
54
+ settings (Settings, optional): The settings object. Defaults to None.
55
+ image_data (dict, optional): keyword image names to binary image data
56
+ """
57
+ settings = Settings(__file__).key("secrets") if settings is None else settings
58
+ required = {"smtp.sender", "smtp.server", "smtp.port", "smtp.password"}
59
+ missing = {r for r in required if r not in settings}
60
+ assert not missing, (
61
+ ", ".join(missing) + " not found in:\n" + "\n".join(settings.search_files)
62
+ )
63
+ sender = settings["smtp.sender"]
64
+ username = settings.get("smtp.username", sender)
65
+ message = MIMEMULTIPART()
66
+ message["Subject"] = subject
67
+ message["From"] = sender
68
+ message["To"] = ", ".join(
69
+ [recipients] if isinstance(recipients, str) else recipients
70
+ )
71
+ message.attach(MIMETEXT(html_body, "html"))
72
+ connection_type = SMTPSSL if settings.get("smtp.ssl", True) else SMTP
73
+
74
+ for name, binary_data in image_data.items():
75
+ image = MIMEIMAGE(binary_data)
76
+ image.add_header("Content-ID", f"<{name}>")
77
+ image.add_header(
78
+ "Content-Disposition",
79
+ "inline",
80
+ filename=name + image_extension(binary_data),
81
+ )
82
+ message.attach(image)
83
+
84
+ with connection_type(settings["smtp.server"], settings["smtp.port"]) as smtp:
85
+ smtp.set_debuglevel(False)
86
+ smtp.login(username, settings["smtp.password"])
87
+ smtp.sendmail(sender, recipients, message.as_string())
88
+ smtp.quit()
@@ -361,10 +361,17 @@ class Settings:
361
361
 
362
362
  def main() -> None:
363
363
  """Get settings values"""
364
- settings = Settings(__file__, dirname(dirname(__file__))).key("secrets")
365
-
366
364
  args = list(ARGV[1:])
367
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
+
368
375
  if "--secrets" in args:
369
376
  args.remove("--secrets")
370
377
 
@@ -380,7 +387,7 @@ def main() -> None:
380
387
  PRINT("\tValue set")
381
388
 
382
389
  for arg in args:
383
- PRINT(settings.get(arg))
390
+ PRINT(f"{settings.get(arg)}")
384
391
 
385
392
 
386
393
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devopsdriver
3
- Version: 0.1.35
3
+ Version: 0.1.36
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.
@@ -52,7 +52,7 @@ Requires-Dist: setuptools==69.0.2
52
52
  Requires-Dist: azure-devops==7.1.0b4
53
53
 
54
54
  ![status sheild](https://img.shields.io/static/v1?label=status&message=starting...&color=inactive&style=plastic)
55
- [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.35&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.35/)
55
+ [![status sheild](https://img.shields.io/static/v1?label=released&message=v0.1.36&color=active&style=plastic)](https://pypi.org/project/devopsdriver/0.1.36/)
56
56
  [![GitHub](https://img.shields.io/github/license/marcpage/devops-driver?style=plastic)](https://github.com/marcpage/devops-driver?tab=Unlicense-1-ov-file#readme)
57
57
  [![GitHub contributors](https://img.shields.io/github/contributors/marcpage/devops-driver?style=flat)](https://github.com/marcpage/devops-driver/graphs/contributors)
58
58
  [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
@@ -2,20 +2,22 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  devopsdriver/__init__.py
5
+ devopsdriver/sendmail.py
5
6
  devopsdriver/settings.py
6
7
  devopsdriver.egg-info/PKG-INFO
7
8
  devopsdriver.egg-info/SOURCES.txt
8
9
  devopsdriver.egg-info/dependency_links.txt
9
10
  devopsdriver.egg-info/requires.txt
10
11
  devopsdriver.egg-info/top_level.txt
11
- devopsdriver/azure/__init__.py
12
- devopsdriver/azure/clients.py
13
- devopsdriver/azure/workitem/__init__.py
14
- devopsdriver/azure/workitem/client.py
15
- devopsdriver/azure/workitem/wiql.py
16
- devopsdriver/azure/workitem/workitem.py
12
+ devopsdriver/azdo/__init__.py
13
+ devopsdriver/azdo/clients.py
14
+ devopsdriver/azdo/workitem/__init__.py
15
+ devopsdriver/azdo/workitem/client.py
16
+ devopsdriver/azdo/workitem/wiql.py
17
+ devopsdriver/azdo/workitem/workitem.py
17
18
  tests/test_azure_clients.py
18
19
  tests/test_azure_workitem.py
19
20
  tests/test_azure_workitem_client.py
20
21
  tests/test_azure_workitem_wiql.py
22
+ tests/test_sendmail.py
21
23
  tests/test_settings.py
@@ -11,7 +11,7 @@ from os.path import join
11
11
  from helpers import setup_settings, write
12
12
 
13
13
  from devopsdriver import Azure
14
- from devopsdriver.azure import clients
14
+ from devopsdriver.azdo import clients
15
15
 
16
16
 
17
17
  class MockConnection: # pylint: disable=too-few-public-methods
@@ -2,7 +2,7 @@
2
2
 
3
3
  """ Test work item """
4
4
 
5
- from devopsdriver.azure import WorkItem
5
+ from devopsdriver.azdo import WorkItem
6
6
 
7
7
 
8
8
  class MockAzureWorkItem:
@@ -4,8 +4,8 @@
4
4
 
5
5
  from types import SimpleNamespace
6
6
 
7
- from devopsdriver.azure.workitem.client import Client
8
- from devopsdriver.azure import Wiql, Equal
7
+ from devopsdriver.azdo.workitem.client import Client
8
+ from devopsdriver.azdo import Wiql, Equal
9
9
 
10
10
 
11
11
  class MockClient: # pylint: disable=too-few-public-methods
@@ -4,11 +4,11 @@
4
4
 
5
5
  from datetime import date, datetime
6
6
 
7
- from devopsdriver.azure import Wiql
8
- from devopsdriver.azure import Ascending, Descending, Value
9
- from devopsdriver.azure import IsEmpty, IsNotEmpty, And, Or
10
- from devopsdriver.azure import GreaterThan, LessThan, Equal, NotEqual
11
- from devopsdriver.azure import GreaterThanOrEqual, LessThanOrEqual
7
+ from devopsdriver.azdo import Wiql
8
+ from devopsdriver.azdo import Ascending, Descending, Value
9
+ from devopsdriver.azdo import IsEmpty, IsNotEmpty, And, Or
10
+ from devopsdriver.azdo import GreaterThan, LessThan, Equal, NotEqual
11
+ from devopsdriver.azdo import GreaterThanOrEqual, LessThanOrEqual
12
12
 
13
13
 
14
14
  def test_no_params() -> None:
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """ Test sendmail """
4
+
5
+ from devopsdriver import sendmail # for debugging
6
+ from devopsdriver import send_email
7
+
8
+
9
+ class MockMultipart(dict):
10
+ """Mock mime multipart"""
11
+
12
+ def __init__(self):
13
+ self.objects = []
14
+ super().__init__()
15
+
16
+ def attach(self, thing):
17
+ """mock attaching"""
18
+ self.objects.append(thing)
19
+
20
+ def as_string(self) -> str:
21
+ """don't actually convert to string so we can check fields"""
22
+ return self
23
+
24
+
25
+ class MockText:
26
+ """mock mime text"""
27
+
28
+ def __init__(self, text: str, kind: str):
29
+ self.text = text
30
+ self.kind = kind
31
+
32
+
33
+ class MockImage:
34
+ """mock mime image"""
35
+
36
+ def __init__(self, data: bytes):
37
+ self.data = data
38
+ self.headers = {}
39
+ self.filenames = {}
40
+
41
+ def add_header(self, key, value, filename=None):
42
+ """store headers and filenames (if they have them)"""
43
+ self.headers[key] = value
44
+
45
+ if filename:
46
+ self.filenames[key] = filename
47
+
48
+
49
+ class MockSmtp:
50
+ """mock smtp and smpt_ssl"""
51
+
52
+ def __init__(self, server: str, port: int):
53
+ self.server = server
54
+ self.port = port
55
+ self.debuglevel = None
56
+ self.username = None
57
+ self.password = None
58
+ self.sender = None
59
+ self.recipients = None
60
+ self.message = None
61
+
62
+ def set_debuglevel(self, level: bool):
63
+ """store that we've set the debug level"""
64
+ self.debuglevel = level
65
+
66
+ def login(self, username: str, password: str):
67
+ """store the credentials"""
68
+ self.username = username
69
+ self.password = password
70
+
71
+ def sendmail(self, sender: str, recipients: list[str], message: str):
72
+ """store what we're sending"""
73
+ self.sender = sender
74
+ self.recipients = recipients
75
+ self.message = message
76
+
77
+ def quit(self):
78
+ """on quit check everything"""
79
+ assert self.server == "smtp.com", self.server
80
+ assert self.port == 1234, self.port
81
+ assert not self.debuglevel and self.debuglevel is not None, self.debuglevel
82
+ assert self.username == "from@domain.com", self.username
83
+ assert self.password == "setec astronomy", self.password
84
+ assert self.sender == "from@domain.com", self.sender
85
+ assert self.recipients == "to@domain.com", self.recipients
86
+ assert len(self.message.objects) == 2, self.message.objects
87
+ assert self.message.objects[0].text == "body", self.message.objects[0].text
88
+ assert self.message.objects[0].kind == "html", self.message.objects[0].kind
89
+ assert (
90
+ self.message.objects[1].data == b"\x89PNG\r\n\x1a\n0000"
91
+ ), self.message.objects[1].data
92
+ assert (
93
+ self.message.objects[1].headers["Content-ID"] == "<image1>"
94
+ ), self.message.objects[1].headers["Content-ID"]
95
+ assert (
96
+ self.message.objects[1].headers["Content-Disposition"] == "inline"
97
+ ), self.message.objects[1].headers["Content-Disposition"]
98
+ assert (
99
+ self.message.objects[1].filenames["Content-Disposition"] == "image1.png"
100
+ ), self.message.objects[1].filenames["Content-Disposition"]
101
+
102
+ def __enter__(self):
103
+ return self
104
+
105
+ def __exit__(self, exc_type, exc_val, exc_tb):
106
+ return False
107
+
108
+
109
+ def test_basic() -> None:
110
+ """test basics"""
111
+ sendmail.MIMEMULTIPART = MockMultipart
112
+ sendmail.MIMETEXT = MockText
113
+ sendmail.MIMEIMAGE = MockImage
114
+ sendmail.SMTP = MockSmtp
115
+ sendmail.SMTPSSL = MockSmtp
116
+ settings = {
117
+ "smtp.sender": "from@domain.com",
118
+ "smtp.server": "smtp.com",
119
+ "smtp.port": 1234,
120
+ "smtp.password": "setec astronomy",
121
+ }
122
+ send_email(
123
+ "to@domain.com", "subject", "body", settings, image1=b"\x89PNG\r\n\x1a\n0000"
124
+ )
125
+
126
+
127
+ def test_unknown_image_type() -> None:
128
+ """test basics"""
129
+ sendmail.MIMEMULTIPART = MockMultipart
130
+ sendmail.MIMETEXT = MockText
131
+ sendmail.MIMEIMAGE = MockImage
132
+ sendmail.SMTP = MockSmtp
133
+ sendmail.SMTPSSL = MockSmtp
134
+ settings = {
135
+ "smtp.sender": "from@domain.com",
136
+ "smtp.server": "smtp.com",
137
+ "smtp.port": 1234,
138
+ "smtp.password": "setec astronomy",
139
+ }
140
+
141
+ try:
142
+ send_email("to@domain.com", "subject", "body", settings, image1=b"bwahaha")
143
+ raise AssertionError("We should have thrown")
144
+
145
+ except AttributeError:
146
+ pass
147
+
148
+
149
+ if __name__ == "__main__":
150
+ test_unknown_image_type()
151
+ test_basic()
@@ -9,7 +9,8 @@ from itertools import product
9
9
 
10
10
  from helpers import setup_settings, ensure, write
11
11
 
12
- import devopsdriver.settings as settings
12
+ from devopsdriver import settings # debug access
13
+ from devopsdriver import Settings
13
14
 
14
15
 
15
16
  def __setup_files(directory: str, dir1: str, dir2: str) -> None:
@@ -40,9 +41,9 @@ def __setup_files(directory: str, dir1: str, dir2: str) -> None:
40
41
  <pref>/test.yaml w
41
42
  <pref>/test.json x
42
43
  """
43
- lin_dir = ensure(settings.Settings.PREF_DIR["Linux"])
44
- mac_dir = ensure(settings.Settings.PREF_DIR["Darwin"])
45
- win_dir = ensure(settings.Settings.PREF_DIR["Windows"])
44
+ lin_dir = ensure(Settings.PREF_DIR["Linux"])
45
+ mac_dir = ensure(Settings.PREF_DIR["Darwin"])
46
+ win_dir = ensure(Settings.PREF_DIR["Windows"])
46
47
  letters = list(ascii_lowercase)
47
48
 
48
49
  for name in ("test", "main"):
@@ -90,9 +91,7 @@ def test_basic():
90
91
  }
91
92
  settings.ARGV = ["exe", "--beta", "bbcli", "--zeta", "zzcli"]
92
93
  opts = (
93
- settings.Settings(
94
- join(base_dir, "main.py"), dir1, dir2, aa=1, bb=2, cc=3
95
- )
94
+ Settings(join(base_dir, "main.py"), dir1, dir2, aa=1, bb=2, cc=3)
96
95
  .cli("bb", "--beta")
97
96
  .cli("zz", "--zeta")
98
97
  .env("aa", "alpha")
@@ -204,11 +203,7 @@ def test_cli_env_in_yaml():
204
203
  "--delta",
205
204
  "cli dd",
206
205
  ]
207
- opts = (
208
- settings.Settings(join(base_dir, "main.py"), aa="code aa")
209
- .cli("cli")
210
- .env("env")
211
- )
206
+ opts = Settings(join(base_dir, "main.py"), aa="code aa").cli("cli").env("env")
212
207
  assert opts["aa"] == "code aa", opts["aa"]
213
208
  assert opts["bb"] == "cli bb", opts["bb"]
214
209
  assert opts["dd"] == "cli dd", opts["dd"]
@@ -237,11 +232,7 @@ def test_environ_values():
237
232
  "HOME": "sweet home",
238
233
  "APPDIR": "app data dir",
239
234
  }
240
- opts = (
241
- settings.Settings(join(base_dir, "main.py"), aa="code aa")
242
- .cli("cli")
243
- .env("env")
244
- )
235
+ opts = Settings(join(base_dir, "main.py"), aa="code aa").cli("cli").env("env")
245
236
  assert opts["output"] == "sweet home/reports", opts["output"]
246
237
  assert opts["settings"] == "app data dir/settings.json", opts["settings"]
247
238
  assert opts["value"] == "testing ${noenv} for noenv", opts["value"]
@@ -256,6 +247,15 @@ def test_main():
256
247
  settings.main()
257
248
 
258
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
259
  def test_secret():
260
260
  """test os secret storage"""
261
261
  with TemporaryDirectory() as working_dir:
@@ -270,9 +270,7 @@ def test_secret():
270
270
  )
271
271
  settings.GET_PASSWORD = lambda s, e: passwords.get(s, {}).get(e, None)
272
272
  write(join(base_dir, "main.yml"), password="main")
273
- opts = settings.Settings(join(base_dir, "main.py")).key(
274
- "password", "system/john"
275
- )
273
+ opts = Settings(join(base_dir, "main.py")).key("password", "system/john")
276
274
  assert opts["password"] == "setec astronomy", opts["password"]
277
275
 
278
276
 
@@ -1,7 +0,0 @@
1
- """ DevOps tools """
2
-
3
- from .azure import Azure
4
-
5
- __version__ = "0.1.35"
6
- __author__ = "Marc Page"
7
- __credits__ = ""
File without changes
File without changes