pyegeria 5.4.2.2__py3-none-any.whl → 5.4.3__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/_output_formats.py +3 -3
- pyegeria/config.py +114 -190
- pyegeria/load_config.py +36 -0
- pyegeria-5.4.3.dist-info/METADATA +163 -0
- {pyegeria-5.4.2.2.dist-info → pyegeria-5.4.3.dist-info}/RECORD +8 -7
- pyegeria-5.4.2.2.dist-info/METADATA +0 -78
- {pyegeria-5.4.2.2.dist-info → pyegeria-5.4.3.dist-info}/LICENSE +0 -0
- {pyegeria-5.4.2.2.dist-info → pyegeria-5.4.3.dist-info}/WHEEL +0 -0
- {pyegeria-5.4.2.2.dist-info → pyegeria-5.4.3.dist-info}/entry_points.txt +0 -0
pyegeria/_output_formats.py
CHANGED
@@ -73,13 +73,13 @@ from loguru import logger
|
|
73
73
|
|
74
74
|
from pyegeria._output_format_models import (Column, Format, ActionParameter, FormatSet, FormatSetDict,
|
75
75
|
save_format_sets_to_json, load_format_sets_from_json)
|
76
|
-
from pyegeria.config import settings
|
76
|
+
# from pyegeria.config import settings
|
77
77
|
|
78
78
|
|
79
79
|
# Get the configured value for the user format sets directory
|
80
80
|
|
81
|
-
USER_FORMAT_SETS_DIR = os.path.expanduser(settings.Environment.pyegeria_user_format_sets_dir)
|
82
|
-
|
81
|
+
# USER_FORMAT_SETS_DIR = os.path.expanduser(settings.Environment.pyegeria_user_format_sets_dir)
|
82
|
+
USER_FORMAT_SETS_DIR = os.getenv("PYEGERIA_USER_FORMAT_SETS_DIR", "./")
|
83
83
|
# Constants
|
84
84
|
MD_SEPARATOR = "\n---\n\n"
|
85
85
|
|
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="
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
#
|
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
|
-
#
|
259
|
-
env_settings =
|
260
|
-
|
261
|
-
#
|
262
|
-
|
263
|
-
|
264
|
-
|
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(
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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"
|
275
|
+
logger.warning(f"Could not read/parse config file '{file_path}': {e}. Continuing with defaults+env.")
|
339
276
|
else:
|
340
|
-
logger.
|
341
|
-
|
342
|
-
#
|
343
|
-
#
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
env
|
359
|
-
|
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[
|
376
|
-
env[
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
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",
|
390
|
-
log["logging_console_format"] = os.getenv(
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
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", "
|
403
|
-
|
404
|
-
|
405
|
-
|
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
|
-
|
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
|
-
|
437
|
-
|
438
|
-
|
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,10 @@ def _parse_list_env(env_var: str, default: List[str]) -> List[str]:
|
|
521
439
|
return default
|
522
440
|
|
523
441
|
|
524
|
-
settings
|
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()
|
pyegeria/load_config.py
ADDED
@@ -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
|
@@ -0,0 +1,163 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: pyegeria
|
3
|
+
Version: 5.4.3
|
4
|
+
Summary: A python client for Egeria
|
5
|
+
License: Apache 2.0
|
6
|
+
Keywords: egeria,metadata,governance
|
7
|
+
Author: Dan Wolfson
|
8
|
+
Author-email: dan.wolfson@pdr-associates.com
|
9
|
+
Requires-Python: >3.12,<4.0.0
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
11
|
+
Classifier: License :: Other/Proprietary License
|
12
|
+
Classifier: Programming Language :: Python
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
15
|
+
Requires-Dist: click
|
16
|
+
Requires-Dist: httpx
|
17
|
+
Requires-Dist: inflect (>=7.5.0,<8.0.0)
|
18
|
+
Requires-Dist: jupyter
|
19
|
+
Requires-Dist: jupyter-notebook-parser (>=0.1.4,<0.2.0)
|
20
|
+
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
21
|
+
Requires-Dist: mermaid-py
|
22
|
+
Requires-Dist: poetry-core (>=2.1.3,<3.0.0)
|
23
|
+
Requires-Dist: psycopg2-binary (>=2.9.9,<3.0.0)
|
24
|
+
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
25
|
+
Requires-Dist: pydantic-settings (>=2.10.1,<3.0.0)
|
26
|
+
Requires-Dist: pydevd-pycharm (>=252.23892.364,<253.0.0)
|
27
|
+
Requires-Dist: pytest (>=8.3.5,<9.0.0)
|
28
|
+
Requires-Dist: requests
|
29
|
+
Requires-Dist: rich
|
30
|
+
Requires-Dist: textual
|
31
|
+
Requires-Dist: trogon (>=0.6.0,<0.7.0)
|
32
|
+
Requires-Dist: urllib3
|
33
|
+
Requires-Dist: validators
|
34
|
+
Requires-Dist: wcwidth (==0.2.13)
|
35
|
+
Project-URL: Homepage, https://github.com/odpi/egeria-python
|
36
|
+
Project-URL: Repository, https://github.com/odpi/egeria-python
|
37
|
+
Description-Content-Type: text/markdown
|
38
|
+
|
39
|
+
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
|
40
|
+
<!-- Copyright Contributors to the ODPi Egeria project. -->
|
41
|
+
|
42
|
+

|
43
|
+
|
44
|
+
[](LICENSE)
|
45
|
+
|
46
|
+
|
47
|
+
# pyegeria: a python client for Egeria
|
48
|
+
|
49
|
+
This is a package for easily using the Egeria
|
50
|
+
open metadata environment from python. Details about the
|
51
|
+
open source Egeria project can be found at [Egeria Project](https://egeria-project.org).
|
52
|
+
|
53
|
+
This package is in active development. There is initial
|
54
|
+
support for many of Egeria's services including configuration and operation. This client depends on
|
55
|
+
This release supports Egeria 5.1 - although most of the functions may work on earlier versions of Egeria as well.
|
56
|
+
|
57
|
+
The code is organized to mimic the existing Egeria Java Client structure.
|
58
|
+
|
59
|
+
The commands folder holds the Egeria Command Line Interface and corresponding commands
|
60
|
+
to visualize and use Egeria. The commands also serve as useful examples.
|
61
|
+
|
62
|
+
An examples folder holds some useful examples showing different facets of using pyegeria.
|
63
|
+
|
64
|
+
WARNING: files that start with "X" are in-progress placeholders that are not meant to be used..they will mature and
|
65
|
+
evolve.
|
66
|
+
|
67
|
+
All feedback is welcome. Please engage via our [community](http://egeria-project.org/guides/community/),
|
68
|
+
team calls, or via github issues in this repo. If interested in contributing,
|
69
|
+
you can engage via the community or directly reach out to
|
70
|
+
[dan.wolfson\@pdr-associates.com](mailto:dan.wolfson@pdr-associates.com?subject=pyegeria).
|
71
|
+
|
72
|
+
This is a learning experience.
|
73
|
+
|
74
|
+
## Configuration
|
75
|
+
|
76
|
+
pyegeria uses a simple, predictable precedence for configuration:
|
77
|
+
|
78
|
+
1. Built-in defaults (Pydantic models in pyegeria.config)
|
79
|
+
2. Config file (JSON) if found
|
80
|
+
3. Environment variables (OS env and optional .env)
|
81
|
+
4. Explicit env file passed to get_app_config/load_app_config
|
82
|
+
|
83
|
+
Environment always overrides config file, which overrides defaults.
|
84
|
+
|
85
|
+
### Where to put your configuration
|
86
|
+
|
87
|
+
- Config file: A JSON file named config.json. The loader looks in this order:
|
88
|
+
- If PYEGERIA_CONFIG_DIRECTORY is set: $PYEGERIA_CONFIG_DIRECTORY/$PYEGERIA_CONFIG_FILE
|
89
|
+
- Else if PYEGERIA_ROOT_PATH is set: $PYEGERIA_ROOT_PATH/$PYEGERIA_CONFIG_FILE
|
90
|
+
- Else: ./config.json (the current working directory)
|
91
|
+
|
92
|
+
- .env file: Optional. If present in the current working directory (.env), variables from it will be loaded. You can also pass a specific env file path to get_app_config(env_file=...) or load_app_config(env_file=...). For sample variables, see config/env in this repo.
|
93
|
+
|
94
|
+
### Common environment variables
|
95
|
+
|
96
|
+
- PYEGERIA_CONFIG_DIRECTORY: directory containing your config.json
|
97
|
+
- PYEGERIA_ROOT_PATH: root folder used to resolve config.json when CONFIG_DIRECTORY is not set
|
98
|
+
- PYEGERIA_CONFIG_FILE: filename of the configuration JSON (default: config.json)
|
99
|
+
- PYEGERIA_CONSOLE_WIDTH: integer console width (e.g., 200 or 280)
|
100
|
+
- EGERIA_PLATFORM_URL, EGERIA_VIEW_SERVER_URL, EGERIA_ENGINE_HOST_URL: URLs for your Egeria servers
|
101
|
+
- EGERIA_USER, EGERIA_USER_PASSWORD: credentials used by some clients
|
102
|
+
- Logging related: PYEGERIA_ENABLE_LOGGING, PYEGERIA_LOG_DIRECTORY, PYEGERIA_CONSOLE_LOG_LVL, PYEGERIA_FILE_LOG_LVL, etc.
|
103
|
+
|
104
|
+
See config/env for more variables and defaults.
|
105
|
+
|
106
|
+
### Example .env
|
107
|
+
|
108
|
+
# PYEGERIA_CONFIG_DIRECTORY=/path/to/configs
|
109
|
+
# PYEGERIA_ROOT_PATH=/path/to/project
|
110
|
+
# PYEGERIA_CONFIG_FILE=config.json
|
111
|
+
# EGERIA_PLATFORM_URL=https://localhost:9443
|
112
|
+
# EGERIA_VIEW_SERVER=qs-view-server
|
113
|
+
# EGERIA_VIEW_SERVER_URL=https://localhost:9443
|
114
|
+
# EGERIA_USER=myuser
|
115
|
+
# EGERIA_USER_PASSWORD=mypassword
|
116
|
+
# PYEGERIA_CONSOLE_WIDTH=280
|
117
|
+
|
118
|
+
Lines starting with # are comments. Quotes are optional; python-dotenv/pydantic-settings handle both.
|
119
|
+
|
120
|
+
### Example config.json (minimal)
|
121
|
+
|
122
|
+
{
|
123
|
+
"Environment": {
|
124
|
+
"Pyegeria Root": ".",
|
125
|
+
"Egeria Platform URL": "https://localhost:9443"
|
126
|
+
},
|
127
|
+
"User Profile": {
|
128
|
+
"Egeria Home Collection": "MyHome"
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
### Programmatic usage
|
133
|
+
|
134
|
+
from pyegeria import get_app_config
|
135
|
+
cfg = get_app_config() # uses OS env and ./.env
|
136
|
+
# or with explicit env file
|
137
|
+
cfg = get_app_config(env_file="/path/to/dev.env")
|
138
|
+
|
139
|
+
# Access values via Pydantic models
|
140
|
+
print(cfg.Environment.egeria_platform_url)
|
141
|
+
print(cfg.Logging.enable_logging)
|
142
|
+
|
143
|
+
### CLI quick checks
|
144
|
+
|
145
|
+
- Validate your env file:
|
146
|
+
python scripts/validate_env.py --env config/env
|
147
|
+
python scripts/validate_env.py # auto-detects ./config/env or ./.env
|
148
|
+
|
149
|
+
- Run tests (requires Poetry):
|
150
|
+
poetry install
|
151
|
+
poetry run pytest -v
|
152
|
+
|
153
|
+
### Troubleshooting
|
154
|
+
|
155
|
+
- If your env doesn’t seem to apply, confirm which config.json is used (the loader checks PYEGERIA_CONFIG_DIRECTORY first, then PYEGERIA_ROOT_PATH, then ./config.json).
|
156
|
+
- .env files are optional. Missing .env is not an error.
|
157
|
+
- You can always override values with OS environment variables (they take precedence over config.json).
|
158
|
+
|
159
|
+
|
160
|
+
|
161
|
+
----
|
162
|
+
License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/),
|
163
|
+
Copyright Contributors to the ODPi Egeria project.
|
@@ -4496,14 +4496,14 @@ pyegeria/_exceptions.py,sha256=1SrnV194V4_YJNnNAU0myTHQ3dhLn4GF2B2gZcj1u90,18153
|
|
4496
4496
|
pyegeria/_exceptions_new.py,sha256=srmrlqoWy7VvOJOhPcYFKW32MCIovgEg5J7PrYDxzQA,19706
|
4497
4497
|
pyegeria/_globals.py,sha256=qSU5hM4uuJZPp-YapEEKxfcdgH9hauc6R7gRkELLroY,1132
|
4498
4498
|
pyegeria/_output_format_models.py,sha256=p9fTYaIa5KyTMIR4-JAbE9g66_gGMPTnUqjIq20Zr1o,14494
|
4499
|
-
pyegeria/_output_formats.py,sha256=
|
4499
|
+
pyegeria/_output_formats.py,sha256=3G0mrgiX6ZIJBv4FSN6hs3fpYReyebZi4VvcZw3Ly6g,35826
|
4500
4500
|
pyegeria/_validators.py,sha256=pNxND0dN2qvyuGE52N74l1Ezfrh2p9Hao2ziR_t1ENI,7425
|
4501
4501
|
pyegeria/asset_catalog_omvs.py,sha256=P6FceMP0FgakGSOt3ePxpEbsF7nnypzo1aQahjdL_94,29021
|
4502
4502
|
pyegeria/automated_curation_omvs.py,sha256=tzwCyXL0Hx8UjryBBWcPoEuBRajXZpLuwPQ1vuOg2yc,130349
|
4503
4503
|
pyegeria/classification_manager_omvs.py,sha256=kMyDP_dtHf2czU4ZlWYrakQtccPoteadlUCpWRRFJ1Q,187235
|
4504
4504
|
pyegeria/collection_manager.py,sha256=Atx-9M-GCJB9Ej0TXlSxB4NOxSZCDuuLKyW04UYANlw,234516
|
4505
4505
|
pyegeria/collection_models.py,sha256=d3DdWONqDdAeuUQgussiCNfvhKIDFpaI35cdW_Tv4_0,5315
|
4506
|
-
pyegeria/config.py,sha256=
|
4506
|
+
pyegeria/config.py,sha256=MZ8_V8A9p8mFgARuk1nuK4z-U1_EeEZTJeESSwu-VzU,20435
|
4507
4507
|
pyegeria/core_omag_server_config.py,sha256=pNQpocICkZx8sRsTw5DPUe-TFyxlIo1U88qqgci_f7I,97764
|
4508
4508
|
pyegeria/create_tech_guid_lists.py,sha256=hf5q8Xrdsz-bqeIW3yTORZ1XB6_BrKzLDWWwC_bNG2g,4811
|
4509
4509
|
pyegeria/data_designer.py,sha256=i7tsR1_7puyW4MGdMHjJRAozz6MYuOn-hdw24O-rmVY,185907
|
@@ -4516,6 +4516,7 @@ pyegeria/feedback_manager_omvs.py,sha256=0xBs0p54vmdfVYYgQ8pOanLC4fxfgTk1Z61Y6D1
|
|
4516
4516
|
pyegeria/full_omag_server_config.py,sha256=CQqLCy_3DZFvJZEOcGf50HWdFaWpiAIs6z-kKyjvpDA,47464
|
4517
4517
|
pyegeria/glossary_manager.py,sha256=H9puosVy1ni0bqJ0sCeRZiKMzvXU8kOvkY5Qwh02CgI,111189
|
4518
4518
|
pyegeria/governance_officer.py,sha256=Omgn1ZLQRAH0Ob5cdLA1P5BcbIpuJ2cIbGnTDovreMc,100285
|
4519
|
+
pyegeria/load_config.py,sha256=XDwPAHB3MvGRuoP8kg1lJJAI4BgMWZ3TYxfxYROgJj4,1188
|
4519
4520
|
pyegeria/load_config_orig.py,sha256=lOM37vdIBcYfLQFTLP5bDuNc7vTFGBNYPfqHtWGNvA4,11624
|
4520
4521
|
pyegeria/logging_configuration.py,sha256=BxTQRN-7OOdk5t1f1xSn8gKU8iT-MfWEgbn6cYWrRsY,7674
|
4521
4522
|
pyegeria/md_processing_helpers.py,sha256=xlQuK5eP_PJqUdy4BScQ97NyBD95jMS3EUg0wK5CsZo,2137
|
@@ -4536,8 +4537,8 @@ pyegeria/template_manager_omvs.py,sha256=chBljs1vy5wr9DRAtbvIt4Cob_7HxGfxLkCNlDT
|
|
4536
4537
|
pyegeria/utils.py,sha256=qgiYEdCRrrL6SpX1sceZQVYR40-rfFAhUJEhsubcx80,6889
|
4537
4538
|
pyegeria/valid_metadata_omvs.py,sha256=Xq9DqBQvBFFJzaFIRKcVZ2k4gJvSh9yeXs_j-O3vn1w,65050
|
4538
4539
|
pyegeria/x_action_author_omvs.py,sha256=RcqSzahUKCtvb_3u_wyintAlc9WFkC_2v0E12TZs8lQ,6433
|
4539
|
-
pyegeria-5.4.
|
4540
|
-
pyegeria-5.4.
|
4541
|
-
pyegeria-5.4.
|
4542
|
-
pyegeria-5.4.
|
4543
|
-
pyegeria-5.4.
|
4540
|
+
pyegeria-5.4.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
4541
|
+
pyegeria-5.4.3.dist-info/METADATA,sha256=bfze_AmW_t2p-wiIuL7_qsIWHY0X7Hd9FSM7Z2FVbBg,6290
|
4542
|
+
pyegeria-5.4.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
4543
|
+
pyegeria-5.4.3.dist-info/entry_points.txt,sha256=HAS-LHaaBfkaZ19XU9g5mXwn2uj2HK99isdijI-VIDk,6353
|
4544
|
+
pyegeria-5.4.3.dist-info/RECORD,,
|
@@ -1,78 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: pyegeria
|
3
|
-
Version: 5.4.2.2
|
4
|
-
Summary: A python client for Egeria
|
5
|
-
License: Apache 2.0
|
6
|
-
Keywords: egeria,metadata,governance
|
7
|
-
Author: Dan Wolfson
|
8
|
-
Author-email: dan.wolfson@pdr-associates.com
|
9
|
-
Requires-Python: >3.12,<4.0.0
|
10
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
11
|
-
Classifier: License :: Other/Proprietary License
|
12
|
-
Classifier: Programming Language :: Python
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
15
|
-
Requires-Dist: click
|
16
|
-
Requires-Dist: httpx
|
17
|
-
Requires-Dist: inflect (>=7.5.0,<8.0.0)
|
18
|
-
Requires-Dist: jupyter
|
19
|
-
Requires-Dist: jupyter-notebook-parser (>=0.1.4,<0.2.0)
|
20
|
-
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
21
|
-
Requires-Dist: mermaid-py
|
22
|
-
Requires-Dist: poetry-core (>=2.1.3,<3.0.0)
|
23
|
-
Requires-Dist: psycopg2-binary (>=2.9.9,<3.0.0)
|
24
|
-
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
25
|
-
Requires-Dist: pydantic-settings (>=2.10.1,<3.0.0)
|
26
|
-
Requires-Dist: pydevd-pycharm (>=252.23892.364,<253.0.0)
|
27
|
-
Requires-Dist: pytest (>=8.3.5,<9.0.0)
|
28
|
-
Requires-Dist: requests
|
29
|
-
Requires-Dist: rich
|
30
|
-
Requires-Dist: textual
|
31
|
-
Requires-Dist: trogon (>=0.6.0,<0.7.0)
|
32
|
-
Requires-Dist: urllib3
|
33
|
-
Requires-Dist: validators
|
34
|
-
Requires-Dist: wcwidth (==0.2.13)
|
35
|
-
Project-URL: Homepage, https://github.com/odpi/egeria-python
|
36
|
-
Project-URL: Repository, https://github.com/odpi/egeria-python
|
37
|
-
Description-Content-Type: text/markdown
|
38
|
-
|
39
|
-
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
|
40
|
-
<!-- Copyright Contributors to the ODPi Egeria project. -->
|
41
|
-
|
42
|
-

|
43
|
-
|
44
|
-
[](LICENSE)
|
45
|
-
|
46
|
-
|
47
|
-
# pyegeria: a python client for Egeria
|
48
|
-
|
49
|
-
This is a package for easily using the Egeria
|
50
|
-
open metadata environment from python. Details about the
|
51
|
-
open source Egeria project can be found at [Egeria Project](https://egeria-project.org).
|
52
|
-
|
53
|
-
This package is in active development. There is initial
|
54
|
-
support for many of Egeria's services including configuration and operation. This client depends on
|
55
|
-
This release supports Egeria 5.1 - although most of the functions may work on earlier versions of Egeria as well.
|
56
|
-
|
57
|
-
The code is organized to mimic the existing Egeria Java Client structure.
|
58
|
-
|
59
|
-
The commands folder holds the Egeria Command Line Interface and corresponding commands
|
60
|
-
to visualize and use Egeria. The commands also serve as useful examples.
|
61
|
-
|
62
|
-
An examples folder holds some useful examples showing different facets of using pyegeria.
|
63
|
-
|
64
|
-
WARNING: files that start with "X" are in-progress placeholders that are not meant to be used..they will mature and
|
65
|
-
evolve.
|
66
|
-
|
67
|
-
All feedback is welcome. Please engage via our [community](http://egeria-project.org/guides/community/),
|
68
|
-
team calls, or via github issues in this repo. If interested in contributing,
|
69
|
-
you can engage via the community or directly reach out to
|
70
|
-
[dan.wolfson\@pdr-associates.com](mailto:dan.wolfson@pdr-associates.com?subject=pyegeria).
|
71
|
-
|
72
|
-
This is a learning experience.
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
----
|
77
|
-
License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/),
|
78
|
-
Copyright Contributors to the ODPi Egeria project.
|
File without changes
|
File without changes
|
File without changes
|