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