pyegeria 5.4.2.3__py3-none-any.whl → 5.4.3.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.
Files changed (27) hide show
  1. commands/cat/debug_log.2025-08-30_21-15-48_528443.log.zip +0 -0
  2. commands/cat/debug_log.log +9410 -1431
  3. commands/cat/dr_egeria_command_help.py +110 -7
  4. md_processing/__init__.py +6 -11
  5. md_processing/data/commands.json +150 -6
  6. md_processing/dr_egeria.py +7 -6
  7. md_processing/dr_egeria_inbox/glossary_test1.md +18 -3
  8. md_processing/dr_egeria_inbox/product.md +11 -11
  9. md_processing/dr_egeria_outbox/friday/processed-2025-08-31 20:57-glossary_test1.md +400 -0
  10. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 09:26-product.md +210 -0
  11. md_processing/md_commands/glossary_commands.py +84 -459
  12. md_processing/md_commands/product_manager_commands.py +10 -7
  13. md_processing/md_commands/project_commands.py +2 -2
  14. md_processing/md_processing_utils/common_md_proc_utils.py +9 -6
  15. md_processing/md_processing_utils/common_md_utils.py +15 -13
  16. md_processing/md_processing_utils/extraction_utils.py +12 -3
  17. md_processing/md_processing_utils/md_processing_constants.py +5 -4
  18. pyegeria/__init__.py +2 -1
  19. pyegeria/config.py +292 -190
  20. pyegeria/glossary_manager.py +2 -2
  21. pyegeria/load_config.py +36 -0
  22. pyegeria-5.4.3.1.dist-info/METADATA +163 -0
  23. {pyegeria-5.4.2.3.dist-info → pyegeria-5.4.3.1.dist-info}/RECORD +26 -22
  24. pyegeria-5.4.2.3.dist-info/METADATA +0 -78
  25. {pyegeria-5.4.2.3.dist-info → pyegeria-5.4.3.1.dist-info}/LICENSE +0 -0
  26. {pyegeria-5.4.2.3.dist-info → pyegeria-5.4.3.1.dist-info}/WHEEL +0 -0
  27. {pyegeria-5.4.2.3.dist-info → pyegeria-5.4.3.1.dist-info}/entry_points.txt +0 -0
pyegeria/config.py CHANGED
@@ -27,7 +27,7 @@ import json
27
27
  from typing import List, Optional, Union, Dict, Any
28
28
 
29
29
  from loguru import logger
30
- from pydantic import BaseModel, Field, validator
30
+ from pydantic import BaseModel, Field, validator, ConfigDict
31
31
  from pydantic_settings import BaseSettings, SettingsConfigDict
32
32
 
33
33
  from pyegeria._exceptions_new import PyegeriaInvalidParameterException, PyegeriaException
@@ -105,15 +105,13 @@ class EnvironmentConfig(BaseModel):
105
105
  egeria_platform_url: str = Field(default="https://localhost:9443", alias="Egeria Platform URL")
106
106
  egeria_view_server_url: str = Field(default="https://localhost:9443", alias="Egeria View Server URL")
107
107
  egeria_view_server: str = Field(default="qs-view-server", alias="Egeria View Server")
108
- pyegeria_root: str = Field(default="/Users/dwolfson/localGit/egeria-v5-3/egeria-python", alias="Pyegeria Root")
108
+ pyegeria_root: str = Field(default="", alias="Pyegeria Root")
109
109
  pyegeria_config_directory: str = Field(default="", alias="Pyegeria Config Directory")
110
110
  pyegeria_config_file: str = Field(default="config.json", alias="Egeria Config File")
111
111
  pyegeria_publishing_root: str = Field(default="/dr-egeria-outbox", alias="Pyegeria Publishing Root")
112
112
  pyegeria_user_format_sets_dir: str = Field(default="~/.pyegeria/format_sets", alias="Pyegeria User Format Sets Dir")
113
113
 
114
- class Config:
115
- populate_by_name = True
116
- extra = "allow"
114
+ model_config = ConfigDict(populate_by_name=True, extra='allow')
117
115
 
118
116
 
119
117
  class DebugConfig(BaseModel):
@@ -122,9 +120,7 @@ class DebugConfig(BaseModel):
122
120
  enable_logger_catch: bool = False
123
121
  timeout_seconds: int = 30
124
122
 
125
- class Config:
126
- populate_by_name = True
127
- extra = "allow"
123
+ model_config = ConfigDict(populate_by_name=True, extra='allow')
128
124
 
129
125
 
130
126
  class LoggingConfig(BaseModel):
@@ -138,9 +134,7 @@ class LoggingConfig(BaseModel):
138
134
  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}"
139
135
  logging_file_format: str = " {time:YYYY-MM-DD HH:mm:ss} | {level} | {function}:{line} - {message }-{extra}"
140
136
 
141
- class Config:
142
- populate_by_name = True
143
- extra = "allow"
137
+ model_config = ConfigDict(populate_by_name=True, extra='allow')
144
138
 
145
139
 
146
140
  class UserProfileConfig(BaseModel):
@@ -151,21 +145,7 @@ class UserProfileConfig(BaseModel):
151
145
  user_name: Optional[str] = "erinoverview"
152
146
  user_pwd: Optional[str] = "secret"
153
147
 
154
- @validator('user_name')
155
- # def validate_user_name(cls, v):
156
- # if not v:
157
- # raise ValueError("Egeria user name is not found in the configuration")
158
- # return v
159
- #
160
- # @validator('user_pwd')
161
- # def validate_user_pwd(cls, v):
162
- # if not v:
163
- # raise ValueError("Egeria user password is not found in the configuration")
164
- # return v
165
-
166
- class Config:
167
- populate_by_name = True
168
- extra = "allow"
148
+ model_config = ConfigDict(populate_by_name=True, extra='allow')
169
149
 
170
150
 
171
151
  class AppConfig(BaseModel):
@@ -210,9 +190,7 @@ class AppConfig(BaseModel):
210
190
  # If not found, return the default
211
191
  return default
212
192
 
213
- class Config:
214
- populate_by_name = True
215
- extra = "allow"
193
+ model_config = ConfigDict(populate_by_name=True, extra='allow')
216
194
 
217
195
 
218
196
  # --- Configuration Loading Logic ---
@@ -220,7 +198,31 @@ class AppConfig(BaseModel):
220
198
  # Private variable to hold the loaded configuration
221
199
  _app_config = None
222
200
 
223
- def load_app_config(env_file: str = None):
201
+ def _resolve_env_settings(env_file: str | None) -> PyegeriaSettings:
202
+ if env_file:
203
+ return PyegeriaSettings.with_env_file(env_file)
204
+ return PyegeriaSettings()
205
+
206
+
207
+ def _find_config_file_path(settings: PyegeriaSettings) -> str | None:
208
+ config_dir = (settings.pyegeria_config_directory or "").strip()
209
+ root_path = (settings.pyegeria_root_path or "").strip()
210
+ config_file = (settings.pyegeria_config_file or "config.json").strip()
211
+
212
+ candidates = []
213
+ if config_dir:
214
+ candidates.append(os.path.join(config_dir, config_file))
215
+ if root_path:
216
+ candidates.append(os.path.join(root_path, config_file))
217
+ candidates.append(os.path.abspath(os.path.join(os.getcwd(), "config.json")))
218
+
219
+ for path in candidates:
220
+ if path and os.path.exists(path):
221
+ return path
222
+ return None
223
+
224
+
225
+ def load_app_config(env_file: str | None = None):
224
226
  """
225
227
  Loads application configuration from files and environment variables.
226
228
  This function should ideally be called only once at application startup.
@@ -246,118 +248,57 @@ def load_app_config(env_file: str = None):
246
248
  # Configuration already loaded, return existing instance
247
249
  return _app_config
248
250
 
249
- # Start with default configuration from Pydantic models
250
- config_dict = {
251
+ # 1) Defaults from models
252
+ config_dict: dict[str, Any] = {
251
253
  "Environment": {},
252
254
  "Debug": {},
253
255
  "Logging": {},
254
256
  "User Profile": {},
255
- "feature_x_enabled": False
257
+ "feature_x_enabled": False,
256
258
  }
257
-
258
- # Initialize env_settings with default values
259
- env_settings = PyegeriaSettings()
260
-
261
- # First check if OS environment variables are set for PYEGERIA_CONFIG_DIRECTORY, PYEGERIA_ROOT_PATH and PYEGERIA_CONFIG_FILE
262
- config_directory = os.getenv("PYEGERIA_CONFIG_DIRECTORY")
263
- root_path = os.getenv("PYEGERIA_ROOT_PATH")
264
- config_file = os.getenv("PYEGERIA_CONFIG_FILE")
265
-
266
- logger.info(f"DEBUG: Initial config_directory from OS env: {config_directory}")
267
- logger.info(f"DEBUG: Initial root_path from OS env: {root_path}")
268
- logger.info(f"DEBUG: Initial config_file from OS env: {config_file}")
269
- logger.info(f"DEBUG: env_file parameter: {env_file}")
270
-
271
- # If env_file is specified, use it to load environment variables
272
- if env_file is not None:
273
- logger.trace(f"DEBUG: Loading environment variables from {env_file}")
274
- env_settings = PyegeriaSettings.with_env_file(env_file)
275
-
276
- # If env_file is specified, always use its values, regardless of OS environment variables
277
- config_directory = env_settings.pyegeria_config_directory
278
- root_path = env_settings.pyegeria_root_path
279
- config_file = env_settings.pyegeria_config_file
280
- # If config_file is set but config_directory and root_path are not, we'll try to load the config file first
281
- # and only check the .env file if we still don't have a config_directory or root_path after loading the config file
282
- elif config_file is not None and config_directory is None and root_path is None:
283
- # We'll check for a .env file later if needed
284
- pass
285
- # If any of config_directory, root_path, or config_file is not set, check for a .env file in the current directory
286
- elif (config_directory is None or root_path is None or config_file is None):
287
- if os.path.exists(".env"):
288
- logger.info("Found .env file")
289
- logger.debug(f"DEBUG: Loading environment variables from .env in current directory")
290
- env_settings = PyegeriaSettings()
291
- logger.debug(f"DEBUG: env_settings.pyegeria_config_directory: {env_settings.pyegeria_config_directory}")
292
- logger.debug(f"DEBUG: env_settings.pyegeria_root_path: {env_settings.pyegeria_root_path}")
293
- logger.debug(f"DEBUG: env_settings.pyegeria_config_file: {env_settings.pyegeria_config_file}")
294
- if config_directory is None:
295
- config_directory = env_settings.pyegeria_config_directory
296
- if root_path is None:
297
- root_path = env_settings.pyegeria_root_path
298
- if config_file is None:
299
- config_file = env_settings.pyegeria_config_file
300
- else:
301
- logger.error(f"The .env file at {env_file} wasn't found")
302
- else:
303
- logger.error(f"The .env file at {env_file} wasn't found-outer")
304
- # Use default values if still not set
305
- if config_directory is None:
306
- config_directory = ""
307
- if root_path is None:
308
- root_path = ""
309
- if config_file is None:
310
- config_file = "config.json"
311
-
312
- # Construct the config file path - prefer config_directory over root_path
313
- if config_directory:
314
- config_file_path = os.path.join(config_directory, config_file)
315
- else:
316
- config_file_path = os.path.join(root_path, config_file)
317
-
318
- if os.path.exists(config_file_path):
319
- logger.info("Found config file at {}".format(config_file_path))
259
+
260
+ # 2) Load env settings from OS/.env according to env_file
261
+ env_settings = _resolve_env_settings(env_file)
262
+
263
+ # 3) Load config file if found
264
+ file_path = _find_config_file_path(env_settings)
265
+ if file_path:
266
+ logger.info(f"Using config file: {file_path}")
320
267
  try:
321
- with open(config_file_path, 'r') as f:
322
- file_config = json.load(f)
323
- config_dict.update(file_config) # Merge/override defaults
324
-
325
- # If config_directory is not set from environment variables or .env file,
326
- # set it from the config file if available
327
- if not config_directory and "Environment" in file_config and "Pyegeria Config Directory" in file_config["Environment"]:
328
- config_directory = file_config["Environment"]["Pyegeria Config Directory"]
329
- logger.debug(f"DEBUG: Setting config_directory from config file: {config_directory}")
330
- # If config_directory is still not set and root_path is not set from environment variables or .env file,
331
- # set root_path from the config file if available
332
- if not config_directory and not root_path and "Environment" in file_config and "Pyegeria Root" in file_config["Environment"]:
333
- root_path = file_config["Environment"]["Pyegeria Root"]
334
- logger.debug(f"DEBUG: Setting root_path from config file: {root_path}")
335
- except json.JSONDecodeError:
336
- logger.warning(f"Warning: Could not parse {config_file_path}. Using defaults/env vars.")
268
+ with open(file_path, "r") as f:
269
+ file_cfg = json.load(f)
270
+ if isinstance(file_cfg, dict):
271
+ config_dict.update(file_cfg)
272
+ else:
273
+ logger.warning("Config file root is not an object; ignoring.")
337
274
  except Exception as e:
338
- logger.warning(f"Warning: Error reading {config_file_path}: {e}. Using defaults/env vars.")
275
+ logger.warning(f"Could not read/parse config file '{file_path}': {e}. Continuing with defaults+env.")
339
276
  else:
340
- logger.warning(f"Warning: Could not find {config_file_path}. Using defaults/env vars.")
341
-
342
- # The root_path has already been set with the correct precedence order:
343
- # 1. If env_file is passed in, use that file to load environment variables
344
- # 2. Otherwise, first check OS environment variables
345
- # 3. If not set, check for a .env file in the current directory
346
- # 4. If still not set, use the default value
347
- # We don't need to set it again here, as that would override the precedence order
348
- env = config_dict.get("Environment", {})
349
-
350
- # Update configuration from environment variables
351
- # Debug section
352
- debug = config_dict["Debug"]
353
- debug['debug_mode'] = _parse_bool_env("PYEGERIA_DEBUG_MODE", debug.get("debug_mode", False))
354
- debug["enable_logger_catch"] = _parse_bool_env("PYEGERIA_ENABLE_LOGGER_CATCH", debug.get("enable_logger_catch", False))
355
- debug["timeout_seconds"] = int(os.getenv("PYEGERIA_TIMEOUT_SECONDS", debug.get("timeout_seconds", 30)))
356
-
357
- # Environment section
358
- env = config_dict["Environment"]
359
- # Use the settings from .env file, but allow OS environment variables to override them
277
+ logger.debug("No config.json found; continuing with defaults + env.")
278
+
279
+ # 4) Overlay environment variables
280
+ # Debug
281
+ dbg = config_dict.setdefault("Debug", {})
282
+ dbg["debug_mode"] = _parse_bool_env("PYEGERIA_DEBUG_MODE", bool(dbg.get("debug_mode", False)))
283
+ dbg["enable_logger_catch"] = _parse_bool_env("PYEGERIA_ENABLE_LOGGER_CATCH", bool(dbg.get("enable_logger_catch", False)))
284
+ dbg["timeout_seconds"] = int(os.getenv("PYEGERIA_TIMEOUT_SECONDS", dbg.get("timeout_seconds", 30)))
285
+
286
+ # Environment
287
+ env = config_dict.setdefault("Environment", {})
288
+ default_root = env.get("Pyegeria Root") or os.getcwd()
289
+ env_config_dir = os.getenv("PYEGERIA_CONFIG_DIRECTORY", env_settings.pyegeria_config_directory or "")
290
+ env_root = os.getenv("PYEGERIA_ROOT_PATH", env_settings.pyegeria_root_path or default_root)
291
+ env_config_file = os.getenv("PYEGERIA_CONFIG_FILE", env_settings.pyegeria_config_file or "config.json")
292
+
293
+ env["Pyegeria Config Directory"] = env_config_dir
294
+ env["pyegeria_config_directory"] = env_config_dir
295
+ env["Pyegeria Root"] = env_root
296
+ env["pyegeria_root"] = env_root
297
+ env["Egeria Config File"] = env_config_file
298
+ env["pyegeria_config_file"] = env_config_file
299
+
360
300
  env["Console Width"] = int(os.getenv("PYEGERIA_CONSOLE_WIDTH", env.get("Console Width", env_settings.pyegeria_console_width)))
301
+ env["console_width"] = env["Console Width"]
361
302
  env["Dr.Egeria Inbox"] = os.getenv("DR_EGERIA_INBOX_PATH", env.get("Dr.Egeria Inbox", "md_processing/dr-egeria-inbox"))
362
303
  env["Dr.Egeria Outbox"] = os.getenv("DR_EGERIA_OUTBOX_PATH", env.get("Dr.Egeria Outbox", "md_processing/dr-egeria-outbox"))
363
304
  env["Egeria Engine Host"] = os.getenv("EGERIA_ENGINE_HOST", env.get("Egeria Engine Host", "qs-engine-host"))
@@ -365,79 +306,56 @@ def load_app_config(env_file: str = None):
365
306
  env["Egeria Glossary Path"] = os.getenv("EGERIA_GLOSSARY_PATH", env.get("Egeria Glossary Path", "glossary"))
366
307
  env["Egeria Integration Daemon"] = os.getenv("EGERIA_INTEGRATION_DAEMON", env.get("Egeria Integration Daemon", "qs-integration-daemon"))
367
308
  env["Egeria Integration Daemon URL"] = os.getenv("EGERIA_INTEGRATION_DAEMON_URL", env.get("Egeria Integration Daemon URL", "https://localhost:9443"))
368
- env["Egeria Jupyter"] = _parse_bool_env("EGERIA_JUPYTER", env.get("Egeria Jupyter", True))
309
+ env["Egeria Jupyter"] = _parse_bool_env("EGERIA_JUPYTER", bool(env.get("Egeria Jupyter", True)))
369
310
  env["Egeria Kafka Endpoint"] = os.getenv("EGERIA_KAFKA", env.get("Egeria Kafka Endpoint", "localhost:9192"))
370
311
  env["Egeria Mermaid Folder"] = os.getenv("EGERIA_MERMAID_FOLDER", env.get("Egeria Mermaid Folder", "mermaid_graphs"))
371
312
  env["Egeria Metadata Store"] = os.getenv("EGERIA_METADATA_STORE", env.get("Egeria Metadata Store", "qs-metadata-store"))
372
313
  env["Egeria Platform URL"] = os.getenv("EGERIA_PLATFORM_URL", env.get("Egeria Platform URL", "https://localhost:9443"))
373
314
  env["Egeria View Server"] = os.getenv("EGERIA_VIEW_SERVER", env.get("Egeria View Server", "qs-view-server"))
374
315
  env["Egeria View Server URL"] = os.getenv("EGERIA_VIEW_SERVER_URL", env.get("Egeria View Server URL", "https://localhost:9443"))
375
- env['Pyegeria Publishing Root'] = os.getenv("PYEGERIA_PUBLISHING_ROOT", env.get("Pyegeria Publishing Root", "/dr-egeria-outbox"))
376
- env['Pyegeria User Format Sets Dir'] = os.getenv("PYEGERIA_USER_FORMAT_SETS_DIR", env.get("Pyegeria User Format Sets Dir", "~/.pyegeria/format_sets"))
377
- # Set Pyegeria Config Directory to the config_directory value we've already determined with the correct precedence order
378
- env["Pyegeria Config Directory"] = config_directory
379
- # Set Pyegeria Root to the root_path value we've already determined with the correct precedence order
380
- env["Pyegeria Root"] = root_path
381
-
382
- # Logging section
383
- log = config_dict["Logging"]
384
- log["console_filter_levels"] = _parse_list_env("PYEGERIA_CONSOLE_FILTER_LEVELS", log.get("console_filter_levels", ["ERROR","WARNING","INFO","SUCCESS"]))
316
+ env["Pyegeria Publishing Root"] = os.getenv("PYEGERIA_PUBLISHING_ROOT", env.get("Pyegeria Publishing Root", "/dr-egeria-outbox"))
317
+ env["Pyegeria User Format Sets Dir"] = os.getenv("PYEGERIA_USER_FORMAT_SETS_DIR", env.get("Pyegeria User Format Sets Dir", "~/.pyegeria/format_sets"))
318
+
319
+ # Logging
320
+ log = config_dict.setdefault("Logging", {})
321
+ log["console_filter_levels"] = _parse_list_env("PYEGERIA_CONSOLE_FILTER_LEVELS", log.get("console_filter_levels", ["ERROR", "WARNING", "INFO", "SUCCESS"]))
385
322
  log["console_logging_enabled"] = _parse_list_env("PYEGERIA_CONSOLE_LOGGING_ENABLED", log.get("console_logging_enabled", ["pyegeria"]))
386
323
  log["console_logging_level"] = os.getenv("PYEGERIA_CONSOLE_LOG_LVL", log.get("console_logging_level", "INFO"))
387
- log["enable_logging"] = _parse_bool_env("PYEGERIA_ENABLE_LOGGING", log.get("enable_logging", False))
324
+ log["enable_logging"] = _parse_bool_env("PYEGERIA_ENABLE_LOGGING", bool(log.get("enable_logging", False)))
388
325
  log["file_logging_level"] = os.getenv("PYEGERIA_FILE_LOG_LVL", log.get("file_logging_level", "INFO"))
389
- log["log_directory"] = os.getenv("PYEGERIA_LOG_DIRECTORY", log.get("log_directory", 'logs'))
390
- log["logging_console_format"] = os.getenv("PYEGERIA_LOGGING_CONSOLE_FORMAT", log.get("logging_console_format",
391
- "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | "
392
- "<cyan>{name}</cyan>:<cyan>{line}</cyan> - "
393
- "<level>{message}</level> -{extra}"))
394
- log["logging_file_format"] = os.getenv("PYEGERIA_LOGGING_FILE_FORMAT", log.get("logging_file_format",
395
- "{time:YYYY-MM-DD HH:mm:ss} | {level} | {function}:{line} "
396
- "- {message}-{extra}"))
397
-
398
- # User Profile section
399
- user = config_dict["User Profile"]
400
- user["Egeria Home Collection"] = os.getenv("EGERIA_HOME_COLLECTION", user.get("Egeria Home Collection", "myHome"))
326
+ log["log_directory"] = os.getenv("PYEGERIA_LOG_DIRECTORY", log.get("log_directory", "logs"))
327
+ log["logging_console_format"] = os.getenv(
328
+ "PYEGERIA_LOGGING_CONSOLE_FORMAT",
329
+ log.get(
330
+ "logging_console_format",
331
+ " <green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | <cyan>{name}</cyan>:<cyan>{line}</cyan> - <level>{message}</level> -{extra}",
332
+ ),
333
+ )
334
+ log["logging_file_format"] = os.getenv(
335
+ "PYEGERIA_LOGGING_FILE_FORMAT",
336
+ log.get("logging_file_format", " {time:YYYY-MM-DD HH:mm:ss} | {level} | {function}:{line} - {message }-{extra}"),
337
+ )
338
+
339
+ # User Profile
340
+ user = config_dict.setdefault("User Profile", {})
341
+ user["Egeria Home Collection"] = os.getenv("EGERIA_HOME_COLLECTION", user.get("Egeria Home Collection", "MyHome"))
401
342
  user["Egeria Home Glossary Name"] = os.getenv("EGERIA_HOME_GLOSSARY_NAME", user.get("Egeria Home Glossary Name", "Egeria-Markdown"))
402
- user["Egeria Local Qualifier"] = os.getenv("EGERIA_LOCAL_QUALIFIER", user.get("Egeria Local Qualifier", "myLocal"))
403
-
404
- # Load user credentials with proper precedence
405
- # 1. Check OS environment variables
406
- user_name = os.getenv("EGERIA_USER")
407
- user_pwd = os.getenv("EGERIA_USER_PASSWORD")
408
-
409
- # 2. If not set, check config file
410
- if user_name is None:
411
- user_name = user.get("user_name")
412
- if user_pwd is None:
413
- user_pwd = user.get("user_pwd")
414
-
415
- # 3. If still not set and we have env_settings, check .env file
416
- if (user_name is None or user_pwd is None) and 'env_settings' in locals():
417
- if user_name is None:
418
- user_name = env_settings.egeria_user_name
419
- if user_pwd is None:
420
- user_pwd = env_settings.egeria_user_password
421
-
422
- # Set the values in the config dictionary
343
+ user["Egeria Local Qualifier"] = os.getenv("EGERIA_LOCAL_QUALIFIER", user.get("Egeria Local Qualifier", "PDR"))
344
+
345
+ user_name = os.getenv("EGERIA_USER", user.get("user_name") or env_settings.egeria_user_name or None)
346
+ user_pwd = os.getenv("EGERIA_USER_PASSWORD", user.get("user_pwd") or env_settings.egeria_user_password or None)
423
347
  user["user_name"] = user_name
424
348
  user["user_pwd"] = user_pwd
425
349
 
426
350
  # Feature flags
427
- # Ensure feature_x_enabled is a boolean, regardless of what's in the config file
428
- feature_x = config_dict.get("feature_x_enabled", False)
429
- feature_x = _parse_bool_value(feature_x)
430
- # Check if the environment variable is set, which would override the config value
431
- if "FEATURE_X_ENABLED" in os.environ:
432
- feature_x = _parse_bool_value(os.getenv("FEATURE_X_ENABLED"))
351
+ feature_x = _parse_bool_value(os.getenv("FEATURE_X_ENABLED", config_dict.get("feature_x_enabled", False)))
433
352
  config_dict["feature_x_enabled"] = feature_x
434
353
 
354
+ # Debug print of env before model creation
435
355
  try:
436
- # Create Pydantic model from the configuration dictionary
437
- # _app_config = AppConfig(**config_dict) <-- why? todo
438
- _app_config = AppConfig()
439
- except ValueError as e:
440
- # Handle validation errors from Pydantic
356
+ logger.info(f"DEBUG ENV SECTION: {config_dict.get('Environment')}")
357
+ _app_config = AppConfig(**config_dict)
358
+ except Exception as e:
441
359
  context = {"caller method": inspect.currentframe().f_back.f_code.co_name}
442
360
  additional_info = {"reason": str(e)}
443
361
  raise PyegeriaInvalidParameterException(None, context, additional_info)
@@ -521,4 +439,188 @@ def _parse_list_env(env_var: str, default: List[str]) -> List[str]:
521
439
  return default
522
440
 
523
441
 
524
- settings = get_app_config()
442
+ # Export a lazily-evaluated settings accessor to avoid import-time side effects
443
+ class _LazySettings:
444
+ def __getattr__(self, name):
445
+ cfg = get_app_config()
446
+ return getattr(cfg, name)
447
+
448
+ settings = _LazySettings()
449
+
450
+
451
+ def pretty_print_config(env_file: str | None = None, safe: bool = True, to_console: bool = True) -> Dict[str, Dict[str, Any]]:
452
+ """
453
+ Pretty print the current configuration and indicate the source of each value
454
+ (Environment, .env file, config file, or default). Uses Rich if available.
455
+
456
+ Args:
457
+ env_file: Optional .env path to force loading before printing (if config not yet loaded).
458
+ safe: Mask sensitive values such as passwords/tokens.
459
+ to_console: If True, prints to console; function always returns a structured dict.
460
+
461
+ Returns:
462
+ dict mapping section -> { key -> {"value": ..., "source": ...} }
463
+ """
464
+ # Ensure config is loaded
465
+ cfg = get_app_config(env_file)
466
+
467
+ # Try import rich lazily
468
+ try:
469
+ from rich.console import Console
470
+ from rich.table import Table
471
+ from rich import box
472
+ console = Console(width=getattr(cfg.Environment, 'console_width', 200))
473
+ use_rich = True
474
+ except Exception:
475
+ console = None
476
+ use_rich = False
477
+
478
+ # Helper to mask sensitive keys
479
+ def _mask(key: str, val: Any) -> Any:
480
+ if not safe:
481
+ return val
482
+ key_l = (key or "").lower()
483
+ if any(s in key_l for s in ["password", "pwd", "secret", "token", "apikey", "api_key"]):
484
+ if val is None:
485
+ return None
486
+ s = str(val)
487
+ if len(s) <= 4:
488
+ return "****"
489
+ return s[:2] + "****" + s[-2:]
490
+ return val
491
+
492
+ # Determine sources. Because we merge defaults, config.json, and env, we infer source:
493
+ # - If an OS env var exists for a specific setting name we used, it's "env".
494
+ # - Else if a config file was found (by our path resolver) and provided an override, mark "config".
495
+ # - Else if value equals model default and neither env nor config provided, "default".
496
+
497
+ # We need to reconstruct which keys are influenced by ENV VAR names.
498
+ env_var_map = {
499
+ # Debug
500
+ ("Debug", "debug_mode"): "PYEGERIA_DEBUG_MODE",
501
+ ("Debug", "enable_logger_catch"): "PYEGERIA_ENABLE_LOGGER_CATCH",
502
+ ("Debug", "timeout_seconds"): "PYEGERIA_TIMEOUT_SECONDS",
503
+ # Environment
504
+ ("Environment", "Console Width"): "PYEGERIA_CONSOLE_WIDTH",
505
+ ("Environment", "Dr.Egeria Inbox"): "DR_EGERIA_INBOX_PATH",
506
+ ("Environment", "Dr.Egeria Outbox"): "DR_EGERIA_OUTBOX_PATH",
507
+ ("Environment", "Egeria Engine Host"): "EGERIA_ENGINE_HOST",
508
+ ("Environment", "Egeria Engine Host URL"): "EGERIA_ENGINE_HOST_URL",
509
+ ("Environment", "Egeria Glossary Path"): "EGERIA_GLOSSARY_PATH",
510
+ ("Environment", "Egeria Integration Daemon"): "EGERIA_INTEGRATION_DAEMON",
511
+ ("Environment", "Egeria Integration Daemon URL"): "EGERIA_INTEGRATION_DAEMON_URL",
512
+ ("Environment", "Egeria Jupyter"): "EGERIA_JUPYTER",
513
+ ("Environment", "Egeria Kafka Endpoint"): "EGERIA_KAFKA",
514
+ ("Environment", "Egeria Mermaid Folder"): "EGERIA_MERMAID_FOLDER",
515
+ ("Environment", "Egeria Metadata Store"): "EGERIA_METADATA_STORE",
516
+ ("Environment", "Egeria Platform URL"): "EGERIA_PLATFORM_URL",
517
+ ("Environment", "Egeria View Server"): "EGERIA_VIEW_SERVER",
518
+ ("Environment", "Egeria View Server URL"): "EGERIA_VIEW_SERVER_URL",
519
+ ("Environment", "Pyegeria Publishing Root"): "PYEGERIA_PUBLISHING_ROOT",
520
+ ("Environment", "Pyegeria User Format Sets Dir"): "PYEGERIA_USER_FORMAT_SETS_DIR",
521
+ ("Environment", "Pyegeria Root"): "PYEGERIA_ROOT_PATH",
522
+ ("Environment", "Pyegeria Config Directory"): "PYEGERIA_CONFIG_DIRECTORY",
523
+ ("Environment", "Egeria Config File"): "PYEGERIA_CONFIG_FILE",
524
+ # Logging
525
+ ("Logging", "console_filter_levels"): "PYEGERIA_CONSOLE_FILTER_LEVELS",
526
+ ("Logging", "console_logging_enabled"): "PYEGERIA_CONSOLE_LOGGING_ENABLED",
527
+ ("Logging", "console_logging_level"): "PYEGERIA_CONSOLE_LOG_LVL",
528
+ ("Logging", "enable_logging"): "PYEGERIA_ENABLE_LOGGING",
529
+ ("Logging", "file_logging_level"): "PYEGERIA_FILE_LOG_LVL",
530
+ ("Logging", "log_directory"): "PYEGERIA_LOG_DIRECTORY",
531
+ ("Logging", "logging_console_format"): "PYEGERIA_LOGGING_CONSOLE_FORMAT",
532
+ ("Logging", "logging_file_format"): "PYEGERIA_LOGGING_FILE_FORMAT",
533
+ # User profile
534
+ ("User Profile", "Egeria Home Collection"): "EGERIA_HOME_COLLECTION",
535
+ ("User Profile", "Egeria Home Glossary Name"): "EGERIA_HOME_GLOSSARY_NAME",
536
+ ("User Profile", "Egeria Local Qualifier"): "EGERIA_LOCAL_QUALIFIER",
537
+ ("User Profile", "user_name"): "EGERIA_USER",
538
+ ("User Profile", "user_pwd"): "EGERIA_USER_PASSWORD",
539
+ # Feature flag example
540
+ (None, "feature_x_enabled"): "FEATURE_X_ENABLED",
541
+ }
542
+
543
+ # Attempt to detect if a config.json was used
544
+ env_settings = _resolve_env_settings(env_file)
545
+ config_file_path = _find_config_file_path(env_settings)
546
+
547
+ # Build a snapshot of defaults by instantiating empty models
548
+ defaults = {
549
+ "Environment": EnvironmentConfig().model_dump(by_alias=True),
550
+ "Debug": DebugConfig().model_dump(by_alias=True),
551
+ "Logging": LoggingConfig().model_dump(by_alias=False),
552
+ "User Profile": UserProfileConfig().model_dump(by_alias=True),
553
+ "feature_x_enabled": False,
554
+ }
555
+
556
+ # Current values from cfg
557
+ sections = [
558
+ ("Environment", cfg.Environment.model_dump(by_alias=True)),
559
+ ("Debug", cfg.Debug.model_dump(by_alias=False)),
560
+ ("Logging", cfg.Logging.model_dump(by_alias=False)),
561
+ ("User Profile", cfg.User_Profile.model_dump(by_alias=True)),
562
+ ]
563
+
564
+ result: Dict[str, Dict[str, Any]] = {}
565
+
566
+ for section_name, values in sections:
567
+ section_out: Dict[str, Any] = {}
568
+ for key, val in values.items():
569
+ # Prefer alias keys in display; ensure key exists in defaults appropriately
570
+ default_section = defaults.get(section_name, {})
571
+ default_val = default_section.get(key, None)
572
+
573
+ # Identify env var used for this key if any
574
+ env_var = env_var_map.get((section_name, key))
575
+ source = "default"
576
+ if env_var and env_var in os.environ:
577
+ source = "env"
578
+ elif config_file_path and (key in (defaults.get(section_name, {}) or {}) or True):
579
+ # If a config file exists and value differs from default and no env var set
580
+ if val != default_val:
581
+ source = "config"
582
+ else:
583
+ source = "default"
584
+ else:
585
+ source = "default"
586
+
587
+ section_out[key] = {
588
+ "value": _mask(key, val),
589
+ "source": source,
590
+ }
591
+ result[section_name] = section_out
592
+
593
+ # Add top-level feature flags
594
+ feat_val = getattr(cfg, "feature_x_enabled", False)
595
+ source = "env" if ("FEATURE_X_ENABLED" in os.environ) else ("config" if config_file_path and feat_val != defaults["feature_x_enabled"] else "default")
596
+ result["feature_x_enabled"] = {"value": _mask("feature_x_enabled", feat_val), "source": source}
597
+
598
+ if to_console:
599
+ if use_rich and console:
600
+ for section_name in ["Environment", "Debug", "Logging", "User Profile"]:
601
+ table = Table(title=f"{section_name} Settings", box=box.SIMPLE_HEAVY)
602
+ table.add_column("Key")
603
+ table.add_column("Value")
604
+ table.add_column("Source")
605
+ for k, info in result[section_name].items():
606
+ table.add_row(str(k), str(info["value"]), info["source"])
607
+ console.print(table)
608
+ # Feature flags
609
+ table = Table(title="Feature Flags", box=box.SIMPLE_HEAVY)
610
+ table.add_column("Key")
611
+ table.add_column("Value")
612
+ table.add_column("Source")
613
+ ff = result["feature_x_enabled"]
614
+ table.add_row("feature_x_enabled", str(ff["value"]), ff["source"])
615
+ console.print(table)
616
+ else:
617
+ # Plain text fallback
618
+ print("Configuration:")
619
+ for section_name in ["Environment", "Debug", "Logging", "User Profile"]:
620
+ print(f"[{section_name}]")
621
+ for k, info in result[section_name].items():
622
+ print(f"- {k}: {info['value']} (source: {info['source']})")
623
+ ff = result["feature_x_enabled"]
624
+ print(f"feature_x_enabled: {ff['value']} (source: {ff['source']})")
625
+
626
+ return result
@@ -2245,9 +2245,9 @@ class GlossaryManager(CollectionManager):
2245
2245
 
2246
2246
  if body is None:
2247
2247
  body = {
2248
- "class": "RelationshipRequestBody",
2248
+ "class": "NewRelationshipRequestBody",
2249
2249
  "properties":
2250
- {"class": "GlossaryTermRelationship", }
2250
+ {"class": "GlossaryTermRelationship" }
2251
2251
  }
2252
2252
 
2253
2253
 
@@ -0,0 +1,36 @@
1
+ """
2
+ Compatibility shim for legacy imports.
3
+ Re-exports configuration loading utilities from pyegeria.config.
4
+ """
5
+ # Import config lazily inside functions to avoid import-time side effects in tests
6
+ from importlib import import_module
7
+
8
+ def load_app_config(env_file: str | None = None):
9
+ return import_module('pyegeria.config').load_app_config(env_file)
10
+
11
+ def get_app_config(env_file: str | None = None):
12
+ return import_module('pyegeria.config').get_app_config(env_file)
13
+
14
+ class PyegeriaSettings:
15
+ def __new__(cls, *args, **kwargs):
16
+ # Construct a real settings instance from pyegeria.config
17
+ return import_module('pyegeria.config').PyegeriaSettings(*args, **kwargs)
18
+
19
+ @classmethod
20
+ def with_env_file(cls, env_file: str):
21
+ return import_module('pyegeria.config').PyegeriaSettings.with_env_file(env_file)
22
+
23
+ # expose the cached instance for tests that reset it directly
24
+ from . import config as _config
25
+
26
+ def __getattr__(name):
27
+ if name == "_app_config":
28
+ return _config._app_config
29
+ raise AttributeError(name)
30
+
31
+
32
+ def __setattr__(name, value):
33
+ if name == "_app_config":
34
+ _config._app_config = value
35
+ else:
36
+ globals()[name] = value