dtPyAppFramework 2.4__tar.gz → 3.0__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 (49) hide show
  1. {dtpyappframework-2.4/src/dtPyAppFramework.egg-info → dtpyappframework-3.0}/PKG-INFO +12 -10
  2. dtpyappframework-3.0/requirements.txt +14 -0
  3. dtpyappframework-3.0/src/dtPyAppFramework/_version.txt +1 -0
  4. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/application.py +16 -2
  5. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/process/__init__.py +71 -14
  6. dtpyappframework-3.0/src/dtPyAppFramework/process/windows_service.py +61 -0
  7. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/secrets/local_secret_store.py +38 -1
  8. {dtpyappframework-2.4 → dtpyappframework-3.0/src/dtPyAppFramework.egg-info}/PKG-INFO +12 -10
  9. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework.egg-info/SOURCES.txt +1 -0
  10. dtpyappframework-3.0/src/dtPyAppFramework.egg-info/requires.txt +16 -0
  11. dtpyappframework-2.4/requirements.txt +0 -12
  12. dtpyappframework-2.4/src/dtPyAppFramework/_version.txt +0 -1
  13. dtpyappframework-2.4/src/dtPyAppFramework.egg-info/requires.txt +0 -12
  14. {dtpyappframework-2.4 → dtpyappframework-3.0}/LICENCE.txt +0 -0
  15. {dtpyappframework-2.4 → dtpyappframework-3.0}/MANIFEST.in +0 -0
  16. {dtpyappframework-2.4 → dtpyappframework-3.0}/README.md +0 -0
  17. {dtpyappframework-2.4 → dtpyappframework-3.0}/pyproject.toml +0 -0
  18. {dtpyappframework-2.4 → dtpyappframework-3.0}/setup.cfg +0 -0
  19. {dtpyappframework-2.4 → dtpyappframework-3.0}/setup.py +0 -0
  20. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/__init__.py +0 -0
  21. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/_description.txt +0 -0
  22. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/_licence.txt +0 -0
  23. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/_metadata.yaml +0 -0
  24. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/_name.txt +0 -0
  25. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/cloud/__init__.py +0 -0
  26. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/cloud/aws.py +0 -0
  27. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/cloud/azure.py +0 -0
  28. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/cloud/cloud_session.py +0 -0
  29. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/decorators/__init__.py +0 -0
  30. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/logging/__init__.py +0 -0
  31. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/logging/default_logging.py +0 -0
  32. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/misc/__init__.py +0 -0
  33. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/misc/packaging/__init__.py +0 -0
  34. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/misc/yaml/__init__.py +0 -0
  35. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/paths/__init__.py +0 -0
  36. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/process/multiprocessing.py +0 -0
  37. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/resources/__init__.py +0 -0
  38. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/__init__.py +0 -0
  39. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/secrets/__init__.py +0 -0
  40. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/secrets/aws_secret_store.py +0 -0
  41. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/secrets/azure_secret_store.py +0 -0
  42. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/secrets/keystore.py +0 -0
  43. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/secrets/local_secret_stores_manager.py +0 -0
  44. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/secrets/secret_store.py +0 -0
  45. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework/settings/settings_reader.py +0 -0
  46. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework.egg-info/dependency_links.txt +0 -0
  47. {dtpyappframework-2.4 → dtpyappframework-3.0}/src/dtPyAppFramework.egg-info/top_level.txt +0 -0
  48. {dtpyappframework-2.4 → dtpyappframework-3.0}/tests/test_keystore.py +0 -0
  49. {dtpyappframework-2.4 → dtpyappframework-3.0}/tests/test_settings_reader.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dtPyAppFramework
3
- Version: 2.4
3
+ Version: 3.0
4
4
  Summary: A Python library for common features in application development.
5
5
  Author-email: Digital-Thought <dev@digital-thought.org>
6
6
  Maintainer-email: Digital-Thought <dev@digital-thought.org>
@@ -49,18 +49,20 @@ Classifier: Topic :: Utilities
49
49
  Requires-Python: >=3.9
50
50
  Description-Content-Type: text/markdown
51
51
  License-File: LICENCE.txt
52
- Requires-Dist: PyYaml
53
- Requires-Dist: colorlog
54
- Requires-Dist: psutil
55
- Requires-Dist: pybase64
56
- Requires-Dist: boto3
57
- Requires-Dist: cryptography
52
+ Requires-Dist: PyYaml~=6.0.2
53
+ Requires-Dist: colorlog~=6.9.0
54
+ Requires-Dist: psutil~=6.1.0
55
+ Requires-Dist: pybase64~=1.4.0
56
+ Requires-Dist: boto3~=1.35.54
57
+ Requires-Dist: cryptography~=44.0.0
58
58
  Requires-Dist: azure-identity
59
59
  Requires-Dist: azure-keyvault-secrets
60
60
  Requires-Dist: pytest-mock
61
61
  Requires-Dist: pytest-watch
62
- Requires-Dist: pytest
63
- Requires-Dist: watchdog
62
+ Requires-Dist: pytest~=8.3.3
63
+ Requires-Dist: watchdog~=6.0.0
64
+ Requires-Dist: azure-core~=1.32.0
65
+ Requires-Dist: pywin32; platform_system == "Windows"
64
66
 
65
67
  # dtPyAppFramework
66
68
 
@@ -0,0 +1,14 @@
1
+ PyYaml~=6.0.2
2
+ colorlog~=6.9.0
3
+ psutil~=6.1.0
4
+ pybase64~=1.4.0
5
+ boto3~=1.35.54
6
+ cryptography~=44.0.0
7
+ azure-identity
8
+ azure-keyvault-secrets
9
+ pytest-mock
10
+ pytest-watch
11
+ pytest~=8.3.3
12
+ watchdog~=6.0.0
13
+ azure-core~=1.32.0
14
+ pywin32;platform_system == "Windows"
@@ -103,21 +103,35 @@ class AbstractApp(object):
103
103
  arg_parser.add_argument('--init', action='store_true', required=False, help='Initialise environment')
104
104
  arg_parser.add_argument('--add_secret', action='store_true', required=False, help='Add secret to store')
105
105
  arg_parser.add_argument('--run', action='store_true', required=False, help='Run Processor')
106
+ arg_parser.add_argument('--service', action='store_true', required=False, help='Run as Service')
106
107
  arg_parser.add_argument('--single_folder', action='store_true', required=False, help='Keeps all Directories in a single folder')
108
+ arg_parser.add_argument('--working_dir', action='store', type=str, required=False, help="Sets the Working Directory")
109
+
107
110
  self.define_args(arg_parser)
108
111
  # Check specific states and add corresponding arguments
109
112
  opts, rem_args = arg_parser.parse_known_args()
110
113
 
114
+ if opts.working_dir:
115
+ os.chdir(opts.working_dir)
116
+
111
117
  if opts.single_folder:
112
118
  os.environ['DEV_MODE'] = "True"
113
119
 
120
+ if opts.service:
121
+ arg_parser.add_argument('--install', action='store_true')
122
+
114
123
  if opts.init:
115
124
  arg_parser.add_argument('--password', action='store', type=str, required=False,
116
125
  help="Secrets Store password")
117
-
118
126
  elif opts.add_secret:
119
127
  arg_parser.add_argument('--name', action='store', type=str, required=True, help="Secret Name")
120
- arg_parser.add_argument('--value', action='store', type=str, required=True, help="Secret Value")
128
+
129
+ group = arg_parser.add_mutually_exclusive_group(required=True)
130
+ group.add_argument('--value', action='store', type=str, help="Secret Value")
131
+ group.add_argument('--file', action='store', type=str, help="File to add to secret")
132
+
133
+ arg_parser.add_argument('--store_as', action='store', type=str, default='raw', choices=['raw', 'base64'],
134
+ help="Store file as either base64 or raw")
121
135
 
122
136
  def exit(self):
123
137
  self.exiting()
@@ -6,11 +6,21 @@ from .. import resources
6
6
  from multiprocessing import current_process
7
7
  from argparse import ArgumentParser
8
8
  from .multiprocessing import MultiProcessingManager
9
-
9
+ import platform
10
10
  import sys
11
11
  import os
12
12
  import logging
13
- import argparse
13
+ import base64
14
+ import threading
15
+ import signal
16
+ import time
17
+
18
+ if platform.system() == "Windows":
19
+ try:
20
+ from dtPyAppFramework.process.windows_service import call_service
21
+ except ImportError as ex:
22
+ logging.error("pywin32 is not installed.")
23
+ raise ex
14
24
 
15
25
 
16
26
  def is_multiprocess_spawned_instance():
@@ -63,6 +73,7 @@ class ProcessManager():
63
73
  self.multiprocessing_manager = None
64
74
  self.stdout_txt_file = None
65
75
  self.stderr_txt_file = None
76
+ self.running = threading.Event()
66
77
 
67
78
  def __initialise_spawned_application__(self, parent_log_path, job_id, worker_id, job_name, pipe_registry):
68
79
  """
@@ -112,10 +123,10 @@ class ProcessManager():
112
123
  stdout_txt = '{}/stdout.txt'.format(self.log_path, self.application_paths.app_short_name)
113
124
  stderr_txt = '{}/stderr.txt'.format(self.log_path, self.application_paths.app_short_name)
114
125
 
115
- stdout_txt_file = open(stdout_txt, mode='w', buffering=1)
116
- stderr_txt_file = open(stderr_txt, mode='w', buffering=1)
117
- sys.stdout = stdout_txt_file
118
- sys.stderr = stderr_txt_file
126
+ self.stdout_txt_file = open(stdout_txt, mode='w', buffering=1)
127
+ self.stderr_txt_file = open(stderr_txt, mode='w', buffering=1)
128
+ sys.stdout = self.stdout_txt_file
129
+ sys.stderr = self.stderr_txt_file
119
130
 
120
131
  def initialise_application(self, arg_parser):
121
132
  """
@@ -147,16 +158,22 @@ class ProcessManager():
147
158
 
148
159
  if args.add_secret:
149
160
  self.__add_secret__(args)
161
+ elif args.service:
162
+ if platform.system() == "Windows":
163
+ sys.argv = [sys.argv[0]]
164
+ call_service(svc_name=self.short_name, svc_display_name=self.full_name,
165
+ svc_description=self.description, main_function=self.__main__,
166
+ exit_function=self.call_shutdown)
167
+
150
168
  else:
169
+ signal.signal(signal.SIGINT, self.call_shutdown)
170
+ signal.signal(signal.SIGTERM, self.call_shutdown)
151
171
  self.__main__(args)
152
172
 
153
173
  except KeyboardInterrupt as kbi:
154
174
  logging.warning('(KeyboardInterrupt) Exiting application.')
155
- if self.exit_procedure:
156
- self.exit_procedure()
157
- if not self.console_app:
158
- self.stdout_txt_file.close()
159
- self.stderr_txt_file.close()
175
+ except Exception as ex:
176
+ logging.exception(str(ex))
160
177
 
161
178
  def load_config(self):
162
179
  """
@@ -173,11 +190,46 @@ class ProcessManager():
173
190
  args: Parsed command-line arguments.
174
191
  """
175
192
  try:
176
- self.application_settings.secrets_manager.set_secret(args.name, args.value)
193
+ name = args.name
194
+ value = None
195
+ if args.value:
196
+ value = args.value
197
+ if args.file:
198
+ file_path = args.file
199
+ if not os.path.exists(file_path):
200
+ raise FileNotFoundError(f'The file "{file_path}" could not be found.')
201
+
202
+ if args.store_as == 'raw':
203
+ with open(file_path, 'r') as file:
204
+ file_content = file.read()
205
+ value = file_content
206
+ elif args.store_as == 'base64':
207
+ with open(file_path, 'rb') as file:
208
+ file_content = file.read()
209
+ value = base64.b64encode(file_content).decode('utf-8')
210
+ else:
211
+ raise ValueError(f'Invalid store_as value: {args.store_as}')
212
+
213
+ if value is None:
214
+ raise ValueError(f'No "value" was specified for {name}')
215
+
216
+ settings.SecretsManager().set_secret(name, value)
217
+ logging.info(f'Added secret "{name}" to Secret Store.')
218
+ settings.Settings().close()
177
219
  except Exception as ex:
178
220
  logging.error(f'Error occurred while adding secret {args.name}. Error: {str(ex)}')
179
221
  raise ex
180
222
 
223
+ def handle_shutdown(self):
224
+ if self.exit_procedure:
225
+ self.exit_procedure()
226
+ if not self.console_app:
227
+ self.stdout_txt_file.close()
228
+ self.stderr_txt_file.close()
229
+
230
+ def call_shutdown(self, signum=None, frame=None):
231
+ self.running.clear()
232
+
181
233
  def __main__(self, args):
182
234
  """
183
235
  Execute the main procedure of the application.
@@ -185,8 +237,13 @@ class ProcessManager():
185
237
  Args:
186
238
  args: Parsed command-line arguments.
187
239
  """
240
+ self.running.set()
188
241
  self.load_config()
189
242
  self.main_procedure(args)
190
243
 
191
- if self.exit_procedure:
192
- self.exit_procedure()
244
+ while self.running.is_set():
245
+ time.sleep(0.5)
246
+
247
+ self.handle_shutdown()
248
+
249
+
@@ -0,0 +1,61 @@
1
+ import platform
2
+ import logging
3
+ import sys
4
+
5
+ if platform.system() == "Windows":
6
+ try:
7
+ import win32service
8
+ import socket
9
+ import win32serviceutil
10
+ import win32event
11
+ import servicemanager
12
+ except ImportError as ex:
13
+ logging.error("pywin32 is not installed.")
14
+ raise ex
15
+
16
+
17
+ class WindowsService(win32serviceutil.ServiceFramework):
18
+ _svc_name_ = None
19
+ _svc_display_name_ = None
20
+ _svc_description_ = None
21
+ _running_function_ = None
22
+ _stop_function_ = None
23
+
24
+ def __init__(self, args):
25
+ win32serviceutil.ServiceFramework.__init__(self, args)
26
+ self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
27
+ socket.setdefaulttimeout(60)
28
+ self.args = args
29
+
30
+ def SvcStop(self):
31
+ self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
32
+ win32event.SetEvent(self.hWaitStop)
33
+ self._stop_function_()
34
+
35
+ def SvcDoRun(self):
36
+ try:
37
+ rc = None
38
+ logging.info(f"Service {self._svc_name_} is starting...")
39
+ servicemanager.LogInfoMsg(f"Service {self._svc_name_} is starting...")
40
+ self._running_function_(self.args)
41
+ while rc != win32event.WAIT_OBJECT_0:
42
+ rc = win32event.WaitForSingleObject(self.hWaitStop, 5000)
43
+ except Exception as ex:
44
+ logging.exception(str(ex))
45
+ raise ex
46
+ logging.info(f"Service {self._svc_name_} is stopping...")
47
+ servicemanager.LogInfoMsg(f"Service {self._svc_name_} is stopping...")
48
+
49
+ def call_service(svc_name, svc_display_name, svc_description, main_function, exit_function):
50
+ WindowsService._svc_name_ = svc_name
51
+ WindowsService._svc_display_name_ = svc_display_name
52
+ WindowsService._svc_description_ = svc_description
53
+ WindowsService._running_function_ = main_function
54
+ WindowsService._stop_function_ = exit_function
55
+
56
+ if len(sys.argv) == 1:
57
+ servicemanager.Initialize()
58
+ servicemanager.PrepareToHostSingle(WindowsService)
59
+ servicemanager.StartServiceCtrlDispatcher()
60
+ else:
61
+ win32serviceutil.HandleCommandLine(WindowsService)
@@ -4,6 +4,8 @@ import sys
4
4
  import logging
5
5
  import re
6
6
  import pybase64
7
+ import yaml
8
+ import base64
7
9
 
8
10
  from .secret_store import AbstractSecretStore, SecretsStoreException
9
11
  from itertools import cycle
@@ -64,9 +66,45 @@ class LocalSecretStore(AbstractSecretStore):
64
66
  self.store_available = True
65
67
  self.store_read_only = not self.__is_writeable()
66
68
  logging.info(f'Successfully opened Secrets Store: {self.store_path}')
69
+ self.__check_auto_imports(root_store_path)
67
70
  except Exception as ex:
68
71
  raise SecretsStoreException(f'Failed to open Secrets Store: {self.store_path}. Error: {str(ex)}')
69
72
 
73
+ def __check_auto_imports(self, root_store_path):
74
+ auto_yaml = os.path.join(root_store_path, 'secrets.yaml')
75
+ if os.path.exists(auto_yaml):
76
+ print(f'Performing Auto-Import of Secrets from {auto_yaml}')
77
+ with open(auto_yaml, 'r', encoding='UTF-8') as auto_yaml_file:
78
+ secrets = yaml.safe_load(auto_yaml_file)
79
+ for entry in secrets['secrets']:
80
+ name = entry.get('name')
81
+ value = entry.get('value')
82
+ secret_file = entry.get('file')
83
+ store_as = entry.get('store_as')
84
+
85
+ if secret_file is not None:
86
+ if os.path.exists(secret_file):
87
+ if store_as == 'raw':
88
+ with open(secret_file, 'r') as file:
89
+ file_content = file.read()
90
+ value = file_content
91
+ elif store_as == 'base64':
92
+ with open(secret_file, 'rb') as file:
93
+ file_content = file.read()
94
+ value = base64.b64encode(file_content).decode('utf-8')
95
+ else:
96
+ print(f'Unsupported "store_as" value of {store_as} for {name}', file=sys.stderr)
97
+ else:
98
+ print(f'The file "{secret_file}" specified for {name} does not exist', file=sys.stderr)
99
+
100
+ if value is not None:
101
+ self.set_secret(name, value)
102
+ print(f'Imported Secret: {name}')
103
+ else:
104
+ print(f'Missing "value" for {name}. Not imported.', file=sys.stderr)
105
+
106
+ os.remove(auto_yaml)
107
+
70
108
  def __guid(self):
71
109
  """
72
110
  Generate a unique identifier based on the machine and store path.
@@ -155,7 +193,6 @@ class LocalSecretStore(AbstractSecretStore):
155
193
  self.__set_index(index)
156
194
 
157
195
  def __set_index(self, index: list):
158
- logging.info(index)
159
196
  self.store.set(key=f'{self.store_name}.INDEX', value=json.dumps(index))
160
197
 
161
198
  def get_index(self) -> list:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dtPyAppFramework
3
- Version: 2.4
3
+ Version: 3.0
4
4
  Summary: A Python library for common features in application development.
5
5
  Author-email: Digital-Thought <dev@digital-thought.org>
6
6
  Maintainer-email: Digital-Thought <dev@digital-thought.org>
@@ -49,18 +49,20 @@ Classifier: Topic :: Utilities
49
49
  Requires-Python: >=3.9
50
50
  Description-Content-Type: text/markdown
51
51
  License-File: LICENCE.txt
52
- Requires-Dist: PyYaml
53
- Requires-Dist: colorlog
54
- Requires-Dist: psutil
55
- Requires-Dist: pybase64
56
- Requires-Dist: boto3
57
- Requires-Dist: cryptography
52
+ Requires-Dist: PyYaml~=6.0.2
53
+ Requires-Dist: colorlog~=6.9.0
54
+ Requires-Dist: psutil~=6.1.0
55
+ Requires-Dist: pybase64~=1.4.0
56
+ Requires-Dist: boto3~=1.35.54
57
+ Requires-Dist: cryptography~=44.0.0
58
58
  Requires-Dist: azure-identity
59
59
  Requires-Dist: azure-keyvault-secrets
60
60
  Requires-Dist: pytest-mock
61
61
  Requires-Dist: pytest-watch
62
- Requires-Dist: pytest
63
- Requires-Dist: watchdog
62
+ Requires-Dist: pytest~=8.3.3
63
+ Requires-Dist: watchdog~=6.0.0
64
+ Requires-Dist: azure-core~=1.32.0
65
+ Requires-Dist: pywin32; platform_system == "Windows"
64
66
 
65
67
  # dtPyAppFramework
66
68
 
@@ -29,6 +29,7 @@ src/dtPyAppFramework/misc/yaml/__init__.py
29
29
  src/dtPyAppFramework/paths/__init__.py
30
30
  src/dtPyAppFramework/process/__init__.py
31
31
  src/dtPyAppFramework/process/multiprocessing.py
32
+ src/dtPyAppFramework/process/windows_service.py
32
33
  src/dtPyAppFramework/resources/__init__.py
33
34
  src/dtPyAppFramework/settings/__init__.py
34
35
  src/dtPyAppFramework/settings/settings_reader.py
@@ -0,0 +1,16 @@
1
+ PyYaml~=6.0.2
2
+ colorlog~=6.9.0
3
+ psutil~=6.1.0
4
+ pybase64~=1.4.0
5
+ boto3~=1.35.54
6
+ cryptography~=44.0.0
7
+ azure-identity
8
+ azure-keyvault-secrets
9
+ pytest-mock
10
+ pytest-watch
11
+ pytest~=8.3.3
12
+ watchdog~=6.0.0
13
+ azure-core~=1.32.0
14
+
15
+ [:platform_system == "Windows"]
16
+ pywin32
@@ -1,12 +0,0 @@
1
- PyYaml
2
- colorlog
3
- psutil
4
- pybase64
5
- boto3
6
- cryptography
7
- azure-identity
8
- azure-keyvault-secrets
9
- pytest-mock
10
- pytest-watch
11
- pytest
12
- watchdog
@@ -1,12 +0,0 @@
1
- PyYaml
2
- colorlog
3
- psutil
4
- pybase64
5
- boto3
6
- cryptography
7
- azure-identity
8
- azure-keyvault-secrets
9
- pytest-mock
10
- pytest-watch
11
- pytest
12
- watchdog
File without changes
File without changes
File without changes