pyegeria 5.4.0.4__py3-none-any.whl → 5.4.0.6__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.
pyegeria/load_config.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  """
3
2
  SPDX-License-Identifier: Apache-2.0
4
3
  Copyright Contributors to the ODPi Egeria project.
@@ -6,212 +5,487 @@ Copyright Contributors to the ODPi Egeria project.
6
5
  This module manages configuration information for pyegeria and pyegeria clients.
7
6
 
8
7
  The load_app_config() function loads configuration information:
9
- 1) Default configuration variables are specified in the Dict structure below.
10
- 2) We construct a path to an external configuration JSON file from the Environment Variables
11
- - PYEGERIA_ROOT_PATH
12
- - PYEGERIA_CONFIG_FILE
13
- 3) If a valid configuration file is found, the configuration will be loaded on top of the default configuration.
14
- 4) We then update the in-memory configuration from Environment Variables, if set.
8
+ 1) Default configuration variables are specified in the Pydantic models below.
9
+ 2) Environment variables are loaded from the .env file using pydantic-settings.
10
+ - PYEGERIA_ROOT_PATH and PYEGERIA_CONFIG_FILE are loaded first to locate the config file
11
+ - Additional environment variables are loaded from the operating system
12
+ 3) We construct a path to an external configuration JSON file from the Environment Variables
13
+ - PYEGERIA_ROOT_PATH
14
+ - PYEGERIA_CONFIG_FILE
15
+ 4) If a valid configuration file is found, the configuration will be loaded on top of the default configuration.
16
+ 5) We then update the in-memory configuration from Environment Variables, if set.
15
17
 
16
18
  The result is that Environment Variable values take priority over configuration file values which override the defaults.
17
19
 
18
20
  The get_app_config() function is used by other modules to get configuration information from the configuration structure
19
- and makes it available as a dict.
21
+ and makes it available as a Pydantic model.
20
22
 
21
23
  """
22
24
  import inspect
23
25
  import os
24
26
  import json
27
+ from typing import List, Optional, Union, Dict, Any
28
+
25
29
  from loguru import logger
26
- from pyegeria._exceptions_new import PyegeriaInvalidParameterException
30
+ from pydantic import BaseModel, Field, validator
31
+ from pydantic_settings import BaseSettings, SettingsConfigDict
32
+
33
+ from pyegeria._exceptions_new import PyegeriaInvalidParameterException, PyegeriaException
34
+
35
+ logger.disable("pyegeria")
36
+ # --- Pydantic Settings for Environment Variables ---
37
+
38
+ class PyegeriaSettings(BaseSettings):
39
+ """
40
+ Settings loaded from environment variables using pydantic-settings.
41
+ This class is used to load environment variables from the .env file.
42
+
43
+ The .env file path can be specified when creating an instance of this class
44
+ by passing the `_env_file` parameter. If not specified, it defaults to ".env"
45
+ in the current directory.
46
+ """
47
+ # Core settings needed to locate the config file
48
+ pyegeria_root_path: str = ""
49
+ pyegeria_config_file: str = "config.json"
50
+
51
+ # Additional settings that can be loaded from .env
52
+ pyegeria_console_width: int = 200
53
+ egeria_user_name: str = ""
54
+ egeria_user_password: str = ""
55
+
56
+ model_config = SettingsConfigDict(
57
+ env_file=".env",
58
+ env_file_encoding="utf-8",
59
+ extra="ignore",
60
+ case_sensitive=False
61
+ )
62
+
63
+ @classmethod
64
+ def with_env_file(cls, env_file: str):
65
+ """
66
+ Create a PyegeriaSettings instance with a specific .env file.
67
+
68
+ Args:
69
+ env_file: Path to the .env file to load
70
+
71
+ Returns:
72
+ PyegeriaSettings: A new PyegeriaSettings instance
73
+ """
74
+ # Create a new class with a custom model_config that specifies the env_file
75
+ class CustomSettings(cls):
76
+ model_config = SettingsConfigDict(
77
+ env_file=env_file,
78
+ env_file_encoding="utf-8",
79
+ extra="ignore",
80
+ case_sensitive=False
81
+ )
82
+
83
+ # Create and return an instance of the custom class
84
+ return CustomSettings()
85
+
86
+
87
+ # --- Pydantic Models for Configuration ---
27
88
 
89
+ class EnvironmentConfig(BaseModel):
90
+ """Environment configuration settings"""
91
+ console_width: int = Field(default=200, alias="Console Width")
92
+ dr_egeria_inbox: str = Field(default="md_processing/dr-egeria-inbox", alias="Dr.Egeria Inbox")
93
+ dr_egeria_outbox: str = Field(default="md_processing/dr-egeria-outbox", alias="Dr.Egeria Outbox")
94
+ egeria_engine_host_url: str = Field(default="", alias="Egeria Engine Host URL")
95
+ egeria_engine_host: str = Field(default="qs-engine-host", alias="Egeria Engine Host")
96
+ egeria_glossary_path: str = Field(default="glossary", alias="Egeria Glossary Path")
97
+ egeria_integration_daemon_url: str = Field(default="https://localhost:9443", alias="Egeria Integration Daemon URL")
98
+ egeria_integration_daemon: str = Field(default="qs-integration-daemon", alias="Egeria Integration Daemon")
99
+ egeria_jupyter: bool = Field(default=True, alias="Egeria Jupyter")
100
+ egeria_kafka_endpoint: str = Field(default="localhost:9192", alias="Egeria Kafka Endpoint")
101
+ egeria_mermaid_folder: str = Field(default="mermaid_graphs", alias="Egeria Mermaid Folder")
102
+ egeria_metadata_store: str = Field(default="qs-metadata-store", alias="Egeria Metadata Store")
103
+ egeria_platform_url: str = Field(default="https://localhost:9443", alias="Egeria Platform URL")
104
+ egeria_view_server_url: str = Field(default="https://localhost:9443", alias="Egeria View Server URL")
105
+ egeria_view_server: str = Field(default="qs-view-server", alias="Egeria View Server")
106
+ pyegeria_root: str = Field(default="/Users/dwolfson/localGit/egeria-v5-3/egeria-python", alias="Pyegeria Root")
107
+
108
+ class Config:
109
+ populate_by_name = True
110
+ extra = "allow"
111
+
112
+
113
+ class DebugConfig(BaseModel):
114
+ """Debug configuration settings"""
115
+ debug_mode: bool = False
116
+ enable_logger_catch: bool = False
117
+ timeout_seconds: int = 30
118
+
119
+ class Config:
120
+ populate_by_name = True
121
+ extra = "allow"
122
+
123
+
124
+ class LoggingConfig(BaseModel):
125
+ """Logging configuration settings"""
126
+ console_filter_levels: List[str] = ["ERROR"]
127
+ console_logging_enabled: List[str] = ["_client_new", "_exceptions_new", "collections_manager_omvs", "tests"]
128
+ console_logging_level: str = "INFO"
129
+ enable_logging: bool = False
130
+ file_logging_level: str = "INFO"
131
+ log_directory: str = "logs"
132
+ logging_console_format: str = " <green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | <cyan>{name}</cyan>:<cyan>{line}</cyan> - <level>{message}</level> -{extra}"
133
+ logging_file_format: str = " {time:YYYY-MM-DD HH:mm:ss} | {level} | {function}:{line} - {message }-{extra}"
134
+
135
+ class Config:
136
+ populate_by_name = True
137
+ extra = "allow"
138
+
139
+
140
+ class UserProfileConfig(BaseModel):
141
+ """User profile configuration settings"""
142
+ egeria_home_collection: str = Field(default="MyHome", alias="Egeria Home Collection")
143
+ egeria_home_glossary_name: str = Field(default="Egeria-Markdown", alias="Egeria Home Glossary Name")
144
+ egeria_local_qualifier: str = Field(default="PDR", alias="Egeria Local Qualifier")
145
+ user_name: str = ""
146
+ user_pwd: str = ""
147
+
148
+ @validator('user_name')
149
+ def validate_user_name(cls, v):
150
+ if not v:
151
+ raise ValueError("Egeria user name is not found in the configuration")
152
+ return v
153
+
154
+ @validator('user_pwd')
155
+ def validate_user_pwd(cls, v):
156
+ if not v:
157
+ raise ValueError("Egeria user password is not found in the configuration")
158
+ return v
159
+
160
+ class Config:
161
+ populate_by_name = True
162
+ extra = "allow"
163
+
164
+
165
+ class AppConfig(BaseModel):
166
+ """Main application configuration"""
167
+ Environment: EnvironmentConfig
168
+ Debug: DebugConfig
169
+ Logging: LoggingConfig
170
+ User_Profile: UserProfileConfig = Field(alias="User Profile")
171
+ feature_x_enabled: bool = False
172
+
173
+ def get(self, key, default=None):
174
+ """
175
+ Dictionary-like get method for backward compatibility.
176
+
177
+ Args:
178
+ key: The key to look up
179
+ default: The default value to return if the key is not found
180
+
181
+ Returns:
182
+ The value for the key if found, otherwise the default value
183
+ """
184
+ # First check if the key is a direct attribute of this model
185
+ if hasattr(self, key):
186
+ return getattr(self, key)
187
+
188
+ # Then check if it's in any of the nested models
189
+ for section in [self.Environment, self.Debug, self.Logging, self.User_Profile]:
190
+ if hasattr(section, key):
191
+ return getattr(section, key)
192
+ # Also check using the original field names (with aliases)
193
+ for field_name, field in section.model_fields.items():
194
+ # In Pydantic v2, the alias is stored in json_schema_extra
195
+ alias = None
196
+ if hasattr(field, 'alias'):
197
+ alias = field.alias
198
+ elif hasattr(field, 'json_schema_extra') and field.json_schema_extra:
199
+ alias = field.json_schema_extra.get('alias')
200
+
201
+ if alias == key:
202
+ return getattr(section, field_name)
203
+
204
+ # If not found, return the default
205
+ return default
206
+
207
+ class Config:
208
+ populate_by_name = True
209
+ extra = "allow"
28
210
 
29
- # from dotenv import load_dotenv # pip install python-dotenv if you use .env files
30
211
 
31
212
  # --- Configuration Loading Logic ---
32
213
 
33
214
  # Private variable to hold the loaded configuration
34
215
  _app_config = None
35
216
 
36
- def load_app_config():
217
+ def load_app_config(env_file: str = None):
37
218
  """
38
219
  Loads application configuration from files and environment variables.
39
220
  This function should ideally be called only once at application startup.
221
+
222
+ The function follows this precedence order for configuration settings:
223
+ 1. If env_file is passed in, it uses that file to load environment variables
224
+ 2. Otherwise, it first checks if OS environment variables are set for PYEGERIA_ROOT_PATH and PYEGERIA_CONFIG_FILE
225
+ 3. If they are not set, it checks for a .env file in the current directory
226
+ 4. It then loads the configuration from the config file if available
227
+ 5. Finally, it updates the configuration with environment variables from the operating system,
228
+ which take precedence over the config file values
229
+
230
+ Args:
231
+ env_file: Optional path to a specific .env file to load. If not specified,
232
+ the function follows the precedence order described above.
233
+
234
+ Returns:
235
+ AppConfig: The loaded configuration as a Pydantic model
40
236
  """
41
- global _app_config # Declare intent to modify the global _app_config
237
+ global _app_config # Declare intent to modify the global _app_config
42
238
 
43
239
  if _app_config is not None:
44
240
  # Configuration already loaded, return existing instance
45
241
  return _app_config
46
242
 
47
-
48
- # Define default configuration values
49
- config = {
50
- "Environment": {
51
- "Console Width": 200,
52
- "Dr.Egeria Inbox": "md_processing/dr-egeria-inbox",
53
- "Dr.Egeria Outbox": "md_processing/dr-egeria-outbox",
54
- "Egeria Engine Host URL": "",
55
- "Egeria Engine Host": "qs-engine-host",
56
- "Egeria Glossary Path": "glossary",
57
- "Egeria Integration Daemon URL": "https://localhost:9443",
58
- "Egeria Integration Daemon": "qs-integration-daemon",
59
- "Egeria Jupyter": True,
60
- "Egeria Kafka Endpoint": "localhost:9192",
61
- "Egeria Mermaid Folder": "mermaid_graphs",
62
- "Egeria Metadata Store": "qs-metadata-store",
63
- "Egeria Platform URL": "https://localhost:9443",
64
- "Egeria View Server URL": "https://localhost:9443",
65
- "Egeria View Server": "qs-view-server",
66
- "Pyegeria Root": "/Users/dwolfson/localGit/egeria-v5-3/egeria-python",
67
- },
68
-
69
- "Debug": {
70
- "debug_mode": False,
71
- "enable_logger_catch": False,
72
- "timeout_seconds": 30,
73
- },
74
- "feature_x_enabled": False,
75
- "Logging": {
76
- "console_filter_levels": [
77
- "ERROR"
78
- ],
79
- "console_logging_enabled": [
80
- "_client_new",
81
- "_exceptions_new",
82
- "collections_manager_omvs"
83
- "tests",
84
- ],
85
- "console_logging_level": "INFO",
86
- "enable_logging": False,
87
- "file_logging_level": "INFO",
88
- "log_directory": "logs",
89
- "logging_console_format":
90
- " <cyan>{name}</cyan>:<cyan>{line}</cyan> - <level>{message}</level> -{extra}",
91
- " <green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> |"
92
- "logging_file_format":
93
- " {time:YYYY-MM-DD HH:mm:ss} | {level} | {function}:{line} - {message }-{extra}"
94
- },
95
- "User Profile": {
96
- "Egeria Home Collection": "MyHome",
97
- "Egeria Home Glossary Name": "Egeria-Markdown",
98
- "Egeria Local Qualifier": "PDR"
99
- }
100
- }
101
-
102
- root_path = os.getenv("PYEGERIA_ROOT_PATH", config["Environment"].get("Pyegeria Root",""))
103
- config_file = os.getenv("PYEGERIA_CONFIG_FILE", "config.json")
104
- config_file_path = os.path.join(root_path,config_file)
243
+ # Start with default configuration from Pydantic models
244
+ config_dict = {
245
+ "Environment": {},
246
+ "Debug": {},
247
+ "Logging": {},
248
+ "User Profile": {},
249
+ "feature_x_enabled": False
250
+ }
251
+
252
+ # Initialize env_settings with default values
253
+ env_settings = PyegeriaSettings()
254
+
255
+ # First check if OS environment variables are set for PYEGERIA_ROOT_PATH and PYEGERIA_CONFIG_FILE
256
+ root_path = os.getenv("PYEGERIA_ROOT_PATH")
257
+ config_file = os.getenv("PYEGERIA_CONFIG_FILE")
258
+
259
+ logger.info(f"DEBUG: Initial root_path from OS env: {root_path}")
260
+ logger.info(f"DEBUG: Initial config_file from OS env: {config_file}")
261
+ logger.info(f"DEBUG: env_file parameter: {env_file}")
262
+
263
+ # If env_file is specified, use it to load environment variables
264
+ if env_file is not None:
265
+ print(f"DEBUG: Loading environment variables from {env_file}")
266
+ env_settings = PyegeriaSettings.with_env_file(env_file)
267
+ print(f"DEBUG: env_settings.pyegeria_root_path: {env_settings.pyegeria_root_path}")
268
+ print(f"DEBUG: env_settings.pyegeria_config_file: {env_settings.pyegeria_config_file}")
269
+ # If env_file is specified, always use its values, regardless of OS environment variables
270
+ root_path = env_settings.pyegeria_root_path
271
+ config_file = env_settings.pyegeria_config_file
272
+ # If config_file is set but root_path is not, we'll try to load the config file first
273
+ # and only check the .env file if we still don't have a root_path after loading the config file
274
+ elif config_file is not None and root_path is None:
275
+ # We'll check for a .env file later if needed
276
+ pass
277
+ # If neither config_file nor root_path is set, check for a .env file in the current directory
278
+ elif (root_path is None or config_file is None):
279
+ if os.path.exists(".env"):
280
+ logger.info("Found .env file")
281
+ logger.debug(f"DEBUG: Loading environment variables from .env in current directory")
282
+ env_settings = PyegeriaSettings()
283
+ logger.debug(f"DEBUG: env_settings.pyegeria_root_path: {env_settings.pyegeria_root_path}")
284
+ logger.debug(f"DEBUG: env_settings.pyegeria_config_file: {env_settings.pyegeria_config_file}")
285
+ if root_path is None:
286
+ root_path = env_settings.pyegeria_root_path
287
+ if config_file is None:
288
+ config_file = env_settings.pyegeria_config_file
289
+ else:
290
+ logger.error(f"The .env file at {env_file} wasn't found")
291
+
292
+ # Use default values if still not set
293
+ if root_path is None:
294
+ root_path = ""
295
+ if config_file is None:
296
+ config_file = "config.json"
297
+
298
+ # Construct the config file path
299
+ config_file_path = os.path.join(root_path, config_file)
300
+
105
301
  if os.path.exists(config_file_path):
106
302
  try:
107
303
  with open(config_file_path, 'r') as f:
108
304
  file_config = json.load(f)
109
- config.update(file_config) # Merge/override defaults
110
- # logger.debug("Configuration file loaded from {}".format(config_file_path))
305
+ config_dict.update(file_config) # Merge/override defaults
306
+
307
+ # If root_path is not set from environment variables or .env file,
308
+ # set it from the config file if available
309
+ if not root_path and "Environment" in file_config and "Pyegeria Root" in file_config["Environment"]:
310
+ root_path = file_config["Environment"]["Pyegeria Root"]
311
+ logger.debug(f"DEBUG: Setting root_path from config file: {root_path}")
111
312
  except json.JSONDecodeError:
112
- print(f"Warning: Could not parse {config_file_path}. Using defaults/env vars.")
313
+ logger.warning(f"Warning: Could not parse {config_file_path}. Using defaults/env vars.")
113
314
  except Exception as e:
114
- print(f"Warning: Error reading {config_file_path}: {e}. Using defaults/env vars.")
315
+ logger.warning(f"Warning: Error reading {config_file_path}: {e}. Using defaults/env vars.")
115
316
  else:
116
317
  logger.warning(f"Warning: Could not find {config_file_path}. Using defaults/env vars.")
117
318
 
118
- debug = config["Debug"]
119
- debug['debug_mode'] = os.getenv("PYEGERIA_DEBUG_MODE", debug.get("debug_mode", False))
120
- debug["enable_logger_catch"] = os.getenv("PYEGERIA_ENABLE_LOGGER_CATCH", debug.get("enable_logger_catch", False))
319
+ # The root_path has already been set with the correct precedence order:
320
+ # 1. If env_file is passed in, use that file to load environment variables
321
+ # 2. Otherwise, first check OS environment variables
322
+ # 3. If not set, check for a .env file in the current directory
323
+ # 4. If still not set, use the default value
324
+ # We don't need to set it again here, as that would override the precedence order
325
+ env = config_dict.get("Environment", {})
326
+
327
+ # Update configuration from environment variables
328
+ # Debug section
329
+ debug = config_dict["Debug"]
330
+ debug['debug_mode'] = _parse_bool_env("PYEGERIA_DEBUG_MODE", debug.get("debug_mode", False))
331
+ debug["enable_logger_catch"] = _parse_bool_env("PYEGERIA_ENABLE_LOGGER_CATCH", debug.get("enable_logger_catch", False))
121
332
  debug["timeout_seconds"] = int(os.getenv("PYEGERIA_TIMEOUT_SECONDS", debug.get("timeout_seconds", 30)))
122
333
 
123
- env = config["Environment"]
124
- env["Console Width"] = int(os.getenv("PYEGERIA_CONSOLE_WIDTH", env.get("EGERIA_WIDTH", 200)))
125
- env["Dr.Egeria Inbox"] = os.getenv("DR_EGERIA_INBOX_PATH", env.get("Dr_EGERIA_INBOX",
126
- "md_processing/dr-egeria-inbox"))
127
- env["Dr.Egeria Outbox"] = os.getenv("DR_EGERIA_OUTBOX_PATH", env.get("DR_EGERIA_OUTBOX",
128
- "md_processing/dr-egeria-outbox"))
334
+ # Environment section
335
+ env = config_dict["Environment"]
336
+ # Use the settings from .env file, but allow OS environment variables to override them
337
+ env["Console Width"] = int(os.getenv("PYEGERIA_CONSOLE_WIDTH", env.get("Console Width", env_settings.pyegeria_console_width)))
338
+ env["Dr.Egeria Inbox"] = os.getenv("DR_EGERIA_INBOX_PATH", env.get("Dr.Egeria Inbox", "md_processing/dr-egeria-inbox"))
339
+ env["Dr.Egeria Outbox"] = os.getenv("DR_EGERIA_OUTBOX_PATH", env.get("Dr.Egeria Outbox", "md_processing/dr-egeria-outbox"))
129
340
  env["Egeria Engine Host"] = os.getenv("EGERIA_ENGINE_HOST", env.get("Egeria Engine Host", "qs-engine-host"))
130
- env["Egeria Engine Host URL"] = os.getenv("EGERIA_ENGINE_HOST_URL",env.get("Egeria Engine Host URL", "https://localhost:9443"))
131
-
341
+ env["Egeria Engine Host URL"] = os.getenv("EGERIA_ENGINE_HOST_URL", env.get("Egeria Engine Host URL", "https://localhost:9443"))
132
342
  env["Egeria Glossary Path"] = os.getenv("EGERIA_GLOSSARY_PATH", env.get("Egeria Glossary Path", "glossary"))
133
343
  env["Egeria Integration Daemon"] = os.getenv("EGERIA_INTEGRATION_DAEMON", env.get("Egeria Integration Daemon", "qs-integration-daemon"))
134
- env["Egeria Integration Daemon URL"] = os.getenv("EGERIA_INTEGRATION_DAEMON_URL",env.get("Egeria Integration Daemon URL", "https://localhost:9443"))
135
- env["Egeria Jupyter"] = os.getenv("EGERIA_JUPYTER", env.get("Egeria Jupyter", True))
136
- env["Egeria Kafka"] = os.getenv("EGERIA_KAFKA", env.get("Egeria Kafka", "https://localhost:9192"))
137
- env["Egeria Mermaid Folder"] = os.getenv("EGERIA_MERMAID_FOLDER", env.get("Egeria Mermaid Folder","mermaid_graphs"))
138
- env["Egeria Metadata Store"] = os.getenv("EGERIA_METADATA_STORE", env.get("Egeria Metadata Store","qs-metadata-store"))
139
- env["Egeria Platform URL"] = os.getenv("EGERIA_PLATFORM_URL", env.get("Egeria Platform URL","https://localhost:9443"))
140
- env["Egeria View Server"] = os.getenv("EGERIA_VIEW_SERVER", env.get("Egeria View Server","qs-view-server"))
141
- env["Egeria VIew Server URL"] = os.getenv("EGERIA_VIEW_SERVER_URL", env.get("Egeria View Server URL","https://localhost:9443"))
344
+ env["Egeria Integration Daemon URL"] = os.getenv("EGERIA_INTEGRATION_DAEMON_URL", env.get("Egeria Integration Daemon URL", "https://localhost:9443"))
345
+ env["Egeria Jupyter"] = _parse_bool_env("EGERIA_JUPYTER", env.get("Egeria Jupyter", True))
346
+ env["Egeria Kafka Endpoint"] = os.getenv("EGERIA_KAFKA", env.get("Egeria Kafka Endpoint", "localhost:9192"))
347
+ env["Egeria Mermaid Folder"] = os.getenv("EGERIA_MERMAID_FOLDER", env.get("Egeria Mermaid Folder", "mermaid_graphs"))
348
+ env["Egeria Metadata Store"] = os.getenv("EGERIA_METADATA_STORE", env.get("Egeria Metadata Store", "qs-metadata-store"))
349
+ env["Egeria Platform URL"] = os.getenv("EGERIA_PLATFORM_URL", env.get("Egeria Platform URL", "https://localhost:9443"))
350
+ env["Egeria View Server"] = os.getenv("EGERIA_VIEW_SERVER", env.get("Egeria View Server", "qs-view-server"))
351
+ env["Egeria View Server URL"] = os.getenv("EGERIA_VIEW_SERVER_URL", env.get("Egeria View Server URL", "https://localhost:9443"))
352
+ # Set Pyegeria Root to the root_path value we've already determined with the correct precedence order
142
353
  env["Pyegeria Root"] = root_path
143
354
 
144
- log = config["Logging"]
145
- log["console_filter_levels"] = os.getenv("PYEGERIA_CONSOLE_FILTER_LEVELS",
146
- log.get("console_filter_levels", ["ERROR"]))
147
- log["console_logging_enabled"] = os.getenv("PYEGERIA_CONSOLE_LOGGING_ENABLED",
148
- log.get("console_logging_enabled", ["tests"]))
149
- log["console_logging_level"] = os.getenv("PYEGERIA_CONSOLE_LOG_LVL", log.get("console_logging_level", None))
150
- log["enable_logging"] = os.getenv("PYEGERIA_ENABLE_LOGGING", log.get("enable_logging", False))
151
- log["file_logging_level"] = os.getenv("PYEGERIA_FILE_LOG_LVL", log.get("file_logging_level","INFO"))
152
- log["log_directory"] = os.getenv("PYEGERIA_LOG_DIRECTORY", log.get("log_directory",'logs'))
355
+ # Logging section
356
+ log = config_dict["Logging"]
357
+ log["console_filter_levels"] = _parse_list_env("PYEGERIA_CONSOLE_FILTER_LEVELS", log.get("console_filter_levels", ["ERROR"]))
358
+ log["console_logging_enabled"] = _parse_list_env("PYEGERIA_CONSOLE_LOGGING_ENABLED", log.get("console_logging_enabled", ["tests"]))
359
+ log["console_logging_level"] = os.getenv("PYEGERIA_CONSOLE_LOG_LVL", log.get("console_logging_level", "INFO"))
360
+ log["enable_logging"] = _parse_bool_env("PYEGERIA_ENABLE_LOGGING", log.get("enable_logging", False))
361
+ log["file_logging_level"] = os.getenv("PYEGERIA_FILE_LOG_LVL", log.get("file_logging_level", "INFO"))
362
+ log["log_directory"] = os.getenv("PYEGERIA_LOG_DIRECTORY", log.get("log_directory", 'logs'))
153
363
  log["logging_console_format"] = os.getenv("PYEGERIA_LOGGING_CONSOLE_FORMAT", log.get("logging_console_format",
154
364
  " <green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | "
155
365
  "<cyan>{name}</cyan>:<cyan>{line}</cyan> - "
156
- "<level>{message}</level> -{extra}" ))
157
- log["logging_file_format"] = os.getenv("PYEGERIA_LOGGING_FILE_FORMAT",log.get("logging_file_format",
366
+ "<level>{message}</level> -{extra}"))
367
+ log["logging_file_format"] = os.getenv("PYEGERIA_LOGGING_FILE_FORMAT", log.get("logging_file_format",
158
368
  " {time:YYYY-MM-DD HH:mm:ss} | {level} | {function}:{line} "
159
- "- {message }-{extra}" ))
369
+ "- {message }-{extra}"))
160
370
 
161
- user = config["User Profile"]
371
+ # User Profile section
372
+ user = config_dict["User Profile"]
162
373
  user["Egeria Home Collection"] = os.getenv("EGERIA_HOME_COLLECTION", user.get("Egeria Home Collection", "myHome"))
163
374
  user["Egeria Home Glossary Name"] = os.getenv("EGERIA_HOME_GLOSSARY_NAME", user.get("Egeria Home Glossary Name", "Egeria-Markdown"))
164
375
  user["Egeria Local Qualifier"] = os.getenv("EGERIA_LOCAL_QUALIFIER", user.get("Egeria Local Qualifier", "myLocal"))
165
- user["user_name"] = os.getenv("EGERIA_USER_NAME", "peterprofile")
166
- user["user_pwd"] = os.getenv("EGERIA_USER_PASSWORD", "secret")
167
-
168
- if not user.get("user_pwd"):
169
- context: dict = {}
170
- context['caller method'] = inspect.currentframe().f_back.f_code.co_name
171
- additional_info: dict = {"reason": "Egeria user password is not found in the environment"}
172
- raise PyegeriaInvalidParameterException(None, context, additional_info)
173
-
376
+
377
+ # Load user credentials with proper precedence
378
+ # 1. Check OS environment variables
379
+ user_name = os.getenv("EGERIA_USER")
380
+ user_pwd = os.getenv("EGERIA_USER_PASSWORD")
381
+
382
+ # 2. If not set, check config file
383
+ if user_name is None:
384
+ user_name = user.get("user_name")
385
+ if user_pwd is None:
386
+ user_pwd = user.get("user_pwd")
387
+
388
+ # 3. If still not set and we have env_settings, check .env file
389
+ if (user_name is None or user_pwd is None) and 'env_settings' in locals():
390
+ if user_name is None:
391
+ user_name = env_settings.egeria_user_name
392
+ if user_pwd is None:
393
+ user_pwd = env_settings.egeria_user_password
394
+
395
+ # Set the values in the config dictionary
396
+ user["user_name"] = user_name
397
+ user["user_pwd"] = user_pwd
174
398
 
399
+ # Feature flags
400
+ # Ensure feature_x_enabled is a boolean, regardless of what's in the config file
401
+ feature_x = config_dict.get("feature_x_enabled", False)
402
+ feature_x = _parse_bool_value(feature_x)
403
+ # Check if the environment variable is set, which would override the config value
404
+ if "FEATURE_X_ENABLED" in os.environ:
405
+ feature_x = _parse_bool_value(os.getenv("FEATURE_X_ENABLED"))
406
+ config_dict["feature_x_enabled"] = feature_x
175
407
 
408
+ try:
409
+ # Create Pydantic model from the configuration dictionary
410
+ _app_config = AppConfig(**config_dict)
411
+ except ValueError as e:
412
+ # Handle validation errors from Pydantic
413
+ context = {"caller method": inspect.currentframe().f_back.f_code.co_name}
414
+ additional_info = {"reason": str(e)}
415
+ raise PyegeriaInvalidParameterException(None, context, additional_info)
176
416
 
177
- # Handle type conversion for env vars (they are always strings)
178
- # if "TIMEOUT_SECONDS" in os.environ:
179
- # try:
180
- # config["timeout_seconds"] = int(os.getenv("TIMEOUT_SECONDS"))
181
- # except ValueError:
182
- # print("Warning: TIMEOUT_SECONDS environment variable is not an integer. Using default.")
183
- #
184
- # if "DEBUG_MODE" in os.environ:
185
- # # Convert string "True", "False", "1", "0" to boolean
186
- # debug_str = os.getenv("DEBUG_MODE").lower()
187
- # config["debug_mode"] = debug_str in ('True', '1', 't', 'y', 'yes', 'on')
188
- #
189
- # if "FEATURE_X_ENABLED" in os.environ:
190
- # feature_x_str = os.getenv("FEATURE_X_ENABLED").lower()
191
- # config["feature_x_enabled"] = feature_x_str in ('True', '1', 't', 'y', 'yes', 'on')
192
-
193
- # 4. Handle sensitive API key (only from environment variable)
194
- # config["api_key"] = os.getenv("MY_SERVICE_API_KEY")
195
- # if config["api_key"] is None:
196
- # print("Error: MY_SERVICE_API_KEY environment variable is critical and not set.")
197
- # # In a production application, you might raise a critical exception here:
198
- # # raise ValueError("MY_SERVICE_API_KEY is not set!")
199
-
200
- _app_config = config # Store the final loaded configuration
201
417
  return _app_config
202
418
 
203
- def get_app_config():
419
+
420
+ def get_app_config(env_file: str = None):
204
421
  """
205
422
  Provides access to the loaded application configuration.
206
423
  Ensures config is loaded if not already (useful for testing or simple scripts).
207
424
  For structured apps, load_app_config() should be called explicitly once at startup.
425
+
426
+ Args:
427
+ env_file: Optional path to a specific .env file to load. If not specified,
428
+ the default .env file in the current directory is used.
429
+
430
+ Returns:
431
+ AppConfig: The loaded configuration as a Pydantic model
208
432
  """
209
433
  if _app_config is None:
210
434
  # If get_app_config is called before load_app_config, load it now.
211
435
  # This can be convenient but explicit loading is generally better.
212
- return load_app_config()
436
+ logger.info(f"The env_file {env_file} is being passed in")
437
+ return load_app_config(env_file)
213
438
  return _app_config
214
439
 
215
- # You can also define constants based on the config for common access,
216
- # but be aware these won't update if the config changes after initial load.
217
- # E.g., API_ENDPOINT = get_app_config().get("api_endpoint")
440
+
441
+ def _parse_bool_env(env_var: str, default: bool) -> bool:
442
+ """
443
+ Parse a boolean environment variable.
444
+
445
+ Args:
446
+ env_var: The name of the environment variable
447
+ default: The default value if the environment variable is not set
448
+
449
+ Returns:
450
+ bool: The parsed boolean value
451
+ """
452
+ if env_var in os.environ:
453
+ value = os.getenv(env_var).lower()
454
+ return value in ('true', '1', 't', 'y', 'yes', 'on')
455
+ return default
456
+
457
+ def _parse_bool_value(value: Any) -> bool:
458
+ """
459
+ Parse a boolean value from any type.
460
+
461
+ Args:
462
+ value: The value to parse
463
+
464
+ Returns:
465
+ bool: The parsed boolean value
466
+ """
467
+ if isinstance(value, bool):
468
+ return value
469
+ if isinstance(value, str):
470
+ return value.lower() in ('true', '1', 't', 'y', 'yes', 'on')
471
+ if isinstance(value, (int, float)):
472
+ return bool(value)
473
+ return False
474
+
475
+
476
+ def _parse_list_env(env_var: str, default: List[str]) -> List[str]:
477
+ """
478
+ Parse a list environment variable (comma-separated).
479
+
480
+ Args:
481
+ env_var: The name of the environment variable
482
+ default: The default value if the environment variable is not set
483
+
484
+ Returns:
485
+ List[str]: The parsed list value
486
+ """
487
+ if env_var in os.environ:
488
+ value = os.getenv(env_var)
489
+ if value:
490
+ return [item.strip() for item in value.split(',')]
491
+ return default