azpaddypy 0.4.0__py3-none-any.whl → 0.4.1__py3-none-any.whl

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.
@@ -6,8 +6,11 @@ __version__ = "0.1.0"
6
6
 
7
7
  from azpaddypy.mgmt.logging import AzureLogger
8
8
  from azpaddypy.mgmt.identity import AzureIdentity
9
+ from azpaddypy.mgmt.local_development import LocalDevelopmentSettings, set_local_dev_environment
9
10
 
10
11
  __all__ = [
11
12
  "AzureLogger",
12
13
  "AzureIdentity",
14
+ "LocalDevelopmentSettings",
15
+ "set_local_dev_environment",
13
16
  ]
@@ -0,0 +1,196 @@
1
+ import os
2
+ import json
3
+ import pathlib
4
+ from typing import Dict, Optional, Union
5
+ from .logging import AzureLogger
6
+
7
+
8
+ class LocalDevelopmentSettings:
9
+ """Manages loading of local development settings from .env and JSON files.
10
+
11
+ This class provides a standardized way to load configuration from
12
+ .env files and Azure Functions-style `local.settings.json` files into
13
+ environment variables, making local development environments consistent
14
+ with deployed Azure environments.
15
+
16
+ It supports overriding existing environment variables and provides clear
17
+ logging for loaded settings.
18
+
19
+ Attributes:
20
+ logger: An instance of AzureLogger for structured logging.
21
+ settings_path: The path to the settings file to be loaded.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ service_name: str = "local_dev_settings",
27
+ service_version: str = "1.0.0",
28
+ logger: Optional[AzureLogger] = None,
29
+ connection_string: Optional[str] = None,
30
+ ):
31
+ """Initializes the LocalDevelopmentSettings manager.
32
+
33
+ Args:
34
+ service_name: The name of the service using the settings manager.
35
+ service_version: The version of the service.
36
+ logger: An optional existing AzureLogger instance.
37
+ connection_string: Application Insights connection string for a new logger.
38
+ """
39
+ if logger:
40
+ self.logger = logger
41
+ else:
42
+ self.logger = AzureLogger(
43
+ service_name=service_name,
44
+ service_version=service_version,
45
+ connection_string=connection_string,
46
+ enable_console_logging=True,
47
+ )
48
+ self.logger.info("LocalDevelopmentSettings initialized.")
49
+
50
+ def load_from_dotenv(
51
+ self,
52
+ dotenv_path: Union[str, pathlib.Path],
53
+ override: bool = False
54
+ ) -> bool:
55
+ """Loads key-value pairs from a .env file into environment variables.
56
+
57
+ Args:
58
+ dotenv_path: The path to the .env file.
59
+ override: If True, existing environment variables will be overwritten.
60
+
61
+ Returns:
62
+ True if the file was loaded successfully, False otherwise.
63
+ """
64
+ dotenv_path = pathlib.Path(dotenv_path)
65
+ if not dotenv_path.is_file():
66
+ self.logger.warning(f".env file not found at {dotenv_path}. Skipping.")
67
+ return False
68
+
69
+ try:
70
+ with open(dotenv_path, "r") as f:
71
+ for line in f:
72
+ line = line.strip()
73
+ if not line or line.startswith("#"):
74
+ continue
75
+
76
+ parts = line.split("=", 1)
77
+ if len(parts) != 2:
78
+ continue
79
+
80
+ key, value = parts[0].strip(), parts[1].strip()
81
+ value = value.replace('"', "")
82
+
83
+ if key not in os.environ or override:
84
+ os.environ[key] = value
85
+ self.logger.debug(f"Loaded from .env: {key}={value[:8]}...")
86
+ else:
87
+ self.logger.debug(f"Skipping from .env (exists): {key}")
88
+
89
+ self.logger.info(f"Successfully loaded settings from {dotenv_path}")
90
+ return True
91
+ except Exception as e:
92
+ self.logger.error(f"Error reading .env file at {dotenv_path}: {e}", exc_info=True)
93
+ return False
94
+
95
+ def load_from_json(
96
+ self,
97
+ json_path: Union[str, pathlib.Path],
98
+ override: bool = False
99
+ ) -> bool:
100
+ """Loads settings from a JSON file (e.g., local.settings.json).
101
+
102
+ The JSON file is expected to have a "Values" key containing a
103
+ dictionary of settings.
104
+
105
+ Args:
106
+ json_path: The path to the JSON settings file.
107
+ override: If True, existing environment variables will be overwritten.
108
+
109
+ Returns:
110
+ True if the file was loaded successfully, False otherwise.
111
+ """
112
+ json_path = pathlib.Path(json_path)
113
+ if not json_path.is_file():
114
+ self.logger.warning(f"JSON settings file not found at {json_path}. Skipping.")
115
+ return False
116
+
117
+ try:
118
+ with open(json_path, "r") as f:
119
+ settings = json.load(f)
120
+
121
+ if "Values" in settings and isinstance(settings["Values"], dict):
122
+ for key, value in settings["Values"].items():
123
+ if key not in os.environ or override:
124
+ os.environ[key] = str(value)
125
+ self.logger.debug(f"Loaded from JSON: {key}={str(value)[:8]}...")
126
+ else:
127
+ self.logger.debug(f"Skipping from JSON (exists): {key}")
128
+ self.logger.info(f"Successfully loaded settings from {json_path}")
129
+ return True
130
+ else:
131
+ self.logger.warning(f"No 'Values' dictionary found in {json_path}. Skipping.")
132
+ return False
133
+ except json.JSONDecodeError:
134
+ self.logger.error(f"Error decoding JSON from {json_path}.", exc_info=True)
135
+ return False
136
+ except Exception as e:
137
+ self.logger.error(f"Error reading JSON file at {json_path}: {e}", exc_info=True)
138
+ return False
139
+
140
+ def apply_settings(
141
+ self,
142
+ settings: Dict[str, str],
143
+ override: bool = True
144
+ ):
145
+ """Applies a dictionary of settings to the environment variables.
146
+
147
+ Args:
148
+ settings: A dictionary of key-value pairs to set as environment variables.
149
+ override: If True, existing environment variables will be overwritten.
150
+ """
151
+ for key, value in settings.items():
152
+ if key not in os.environ or override:
153
+ os.environ[key] = str(value)
154
+ self.logger.debug(f"Applied setting: {key}={str(value)[:8]}...")
155
+ else:
156
+ self.logger.debug(f"Skipping setting (exists): {key}")
157
+ self.logger.info(f"Applied {len(settings)} settings to environment.")
158
+
159
+
160
+ def set_local_dev_environment(
161
+ file_path: str = ".env",
162
+ settings: Optional[Dict[str, str]] = None,
163
+ logger: Optional[AzureLogger] = None,
164
+ override_json: bool = True,
165
+ override_dotenv: bool = True,
166
+ override_settings: bool = True
167
+ ):
168
+ """Convenience function to load settings from multiple sources.
169
+
170
+ This function orchestrates loading settings from a `local.settings.json` file,
171
+ a `.env` file, and a direct dictionary of settings, in that order.
172
+
173
+ Args:
174
+ file_path: Base path for `.env` or `local.settings.json`. The function
175
+ will look for both.
176
+ settings: A dictionary of settings to apply.
177
+ logger: An optional AzureLogger instance.
178
+ override_json: Whether settings from JSON should override existing env vars.
179
+ override_dotenv: Whether settings from .env should override existing env vars.
180
+ override_settings: Whether settings from the dictionary should override.
181
+ """
182
+ manager = LocalDevelopmentSettings(logger=logger)
183
+
184
+ # Try loading local.settings.json
185
+ json_path = pathlib.Path(file_path).parent / "local.settings.json"
186
+ if json_path.is_file():
187
+ manager.load_from_json(json_path, override=override_json)
188
+
189
+ # Try loading .env
190
+ dotenv_path = pathlib.Path(file_path)
191
+ if dotenv_path.is_file() and dotenv_path.name == ".env":
192
+ manager.load_from_dotenv(dotenv_path, override=override_dotenv)
193
+
194
+ # Apply dictionary settings
195
+ if settings:
196
+ manager.apply_settings(settings, override=override_settings)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: azpaddypy
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Comprehensive Python logger for Azure, integrating OpenTelemetry for advanced, structured, and distributed tracing.
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Operating System :: OS Independent
@@ -1,11 +1,12 @@
1
1
  azpaddypy/__init__.py,sha256=hrWNAh4OHZOvm3Pbhq5eUjO-pSRYn0h0W0J87tc-lNI,45
2
- azpaddypy/mgmt/__init__.py,sha256=-jH8Ftx9C8qu4yF5dMVEapVZhNwG7m4QCUjyutesOoY,278
2
+ azpaddypy/mgmt/__init__.py,sha256=YsY0KzTcPAjQJZpcQKyVCtS7_rI_AbxVJuD-6TVB3uY,443
3
3
  azpaddypy/mgmt/identity.py,sha256=mA_krQslMsK_sDob-z-QA0B9khK_JUO2way7xwPopR8,12001
4
+ azpaddypy/mgmt/local_development.py,sha256=5nL-EWUk_6EvJUFjCwfYFK14E3Yh-ErfswHJrRoV5aI,7779
4
5
  azpaddypy/mgmt/logging.py,sha256=3ZLSKwpX7Tprthrkm3uN4ph2n2CxiGYUNri7jBJuXEY,36514
5
6
  azpaddypy/resources/__init__.py,sha256=Bvt3VK4RqwoxYpoh6EbLXIR18RuFPKaLP6zLL-icyFk,314
6
7
  azpaddypy/resources/keyvault.py,sha256=4J08vLqoLFd1_UUDBji2oG2fatZaPkgnRyT_Z6wHAOc,20312
7
- azpaddypy-0.4.0.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
8
- azpaddypy-0.4.0.dist-info/METADATA,sha256=_rscPv9TjFvF_lmOgCisBK0janjJiuU7wjQX_H7Empg,665
9
- azpaddypy-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- azpaddypy-0.4.0.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
11
- azpaddypy-0.4.0.dist-info/RECORD,,
8
+ azpaddypy-0.4.1.dist-info/licenses/LICENSE,sha256=hQ6t0g2QaewGCQICHqTckBFbMVakGmoyTAzDpmEYV4c,1089
9
+ azpaddypy-0.4.1.dist-info/METADATA,sha256=8y3xmkrpmT_dNhwI_8a7U7r4Rzy-2EcliNMmm4Bt7cY,665
10
+ azpaddypy-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ azpaddypy-0.4.1.dist-info/top_level.txt,sha256=hsDuboDhT61320ML8X479ezSTwT3rrlDWz1_Z45B2cs,10
12
+ azpaddypy-0.4.1.dist-info/RECORD,,