11import os
22import re
33import sys
4+ from typing import Any , Dict , Optional
45
56import yaml
67
78from app .utils .message import MISSING_CONFIG_KEY_MESSAGE
89
910
10- class Config :
11- def __init__ (self , default_env = "PRODUCTION" ):
12- self .default_env = default_env
13- self ._yaml_config = None
14- self ._user_config_file = None
15- self ._cache = {}
11+ def get_config_file_path (default_env : str = "PRODUCTION" ) -> str :
12+ """Get the path to the config file based on environment"""
13+ config_file = "config.dev.yaml" if default_env .upper () == "DEVELOPMENT" else "config.prod.yaml"
14+
15+ if getattr (sys , "frozen" , False ) and hasattr (sys , "_MEIPASS" ):
16+ return os .path .join (sys ._MEIPASS , "helpers" , config_file )
17+ else :
18+ return os .path .abspath (os .path .join (os .path .dirname (__file__ ), "../../../helpers" , config_file ))
19+
20+
21+ def load_config_file (config_file_path : str ) -> Dict [str , Any ]:
22+ """Load YAML config file"""
23+ with open (config_file_path , "r" ) as f :
24+ return yaml .safe_load (f ) or {}
25+
26+
27+ def get_active_config (user_config_file : Optional [str ] = None , default_env : str = "PRODUCTION" ) -> Dict [str , Any ]:
28+ """Get the active config (user config if provided, else default)"""
29+ if user_config_file :
30+ if not os .path .exists (user_config_file ):
31+ raise FileNotFoundError (f"Config file not found: { user_config_file } " )
32+ return load_config_file (user_config_file )
33+
34+ config_file_path = get_config_file_path (default_env )
35+ return load_config_file (config_file_path )
36+
37+
38+ def get_env (default_env : str = "PRODUCTION" ) -> str :
39+ """Get environment from ENV variable or default"""
40+ return os .environ .get ("ENV" , default_env )
41+
42+
43+ def is_development (default_env : str = "PRODUCTION" ) -> bool :
44+ """Check if current environment is development"""
45+ return get_env (default_env ).upper () == "DEVELOPMENT"
46+
47+
48+ def get_config_value (
49+ config : Dict [str , Any ],
50+ path : str ,
51+ cache : Optional [Dict [str , Any ]] = None
52+ ) -> Any :
53+ """Get config value using dot notation path"""
54+ if cache is None :
55+ cache = {}
56+
57+ if path in cache :
58+ return cache [path ]
59+
60+ keys = path .split ("." )
61+ value = config
62+ for key in keys :
63+ if isinstance (value , dict ) and key in value :
64+ value = value [key ]
65+ else :
66+ raise KeyError (MISSING_CONFIG_KEY_MESSAGE .format (path = path , key = key ))
67+
68+ if isinstance (value , str ):
69+ value = expand_env_placeholders (value )
70+
71+ cache [path ] = value
72+ return value
1673
17- config_file = "config.dev.yaml" if default_env .upper () == "DEVELOPMENT" else "config.prod.yaml"
1874
19- if getattr (sys , "frozen" , False ) and hasattr (sys , "_MEIPASS" ):
20- self ._yaml_path = os .path .join (sys ._MEIPASS , "helpers" , config_file )
21- else :
22- self ._yaml_path = os .path .abspath (os .path .join (os .path .dirname (__file__ ), "../../../helpers" , config_file ))
23-
24- def get_env (self ):
25- return os .environ .get ("ENV" , self .default_env )
26-
27- def is_development (self ):
28- return self .get_env ().upper () == "DEVELOPMENT"
29-
30- def load_user_config (self , config_file : str ):
31- """Set user config file to replace default config."""
32- if config_file and not os .path .exists (config_file ):
33- raise FileNotFoundError (f"Config file not found: { config_file } " )
34- self ._user_config_file = config_file
35- self ._yaml_config = None
36- self ._cache = {}
37-
38- def _get_active_config (self ):
39- """Get the active config (user config if provided, else default)."""
40- if self ._user_config_file :
41- if self ._yaml_config is None :
42- with open (self ._user_config_file , "r" ) as f :
43- self ._yaml_config = yaml .safe_load (f )
44- return self ._yaml_config
45-
46- if self ._yaml_config is None :
47- with open (self ._yaml_path , "r" ) as f :
48- self ._yaml_config = yaml .safe_load (f )
49- return self ._yaml_config
50-
51- def get (self , path : str ):
52- """Get config value using dot notation path."""
53- if path in self ._cache :
54- return self ._cache [path ]
55-
56- config = self ._get_active_config ()
57- keys = path .split ("." )
58- for key in keys :
59- if isinstance (config , dict ) and key in config :
60- config = config [key ]
61- else :
62- raise KeyError (MISSING_CONFIG_KEY_MESSAGE .format (path = path , key = key ))
63-
64- if isinstance (config , str ):
65- config = expand_env_placeholders (config )
66-
67- self ._cache [path ] = config
68- return config
69-
70- def get_service_env_values (self , service_env_path : str ):
71- """Get service environment values as a dictionary."""
72- env_config = self .get (service_env_path )
73- if not isinstance (env_config , dict ):
74- raise ValueError (f"Expected dictionary at path '{ service_env_path } '" )
75- return {key : expand_env_placeholders (value ) if isinstance (value , str ) else value for key , value in env_config .items ()}
76-
77- def load_yaml_config (self ):
78- """Return the active config dict (for backward compatibility)."""
79- return self ._get_active_config ()
80-
81- def get_yaml_value (self , path : str ):
82- """Alias for get() for backward compatibility."""
83- return self .get (path )
84-
85- def unflatten_config (self , flattened_config : dict ) -> dict :
86- """Convert flattened config back to nested structure."""
87- nested = {}
88- for key , value in flattened_config .items ():
89- keys = key .split ("." )
90- current = nested
91- for k in keys [:- 1 ]:
92- if k not in current :
93- current [k ] = {}
94- current = current [k ]
95- current [keys [- 1 ]] = value
96- return nested
75+ def get_service_env_values (config : Dict [str , Any ], service_env_path : str ) -> Dict [str , Any ]:
76+ """Get service environment values as a dictionary"""
77+ env_config = get_config_value (config , service_env_path )
78+ if not isinstance (env_config , dict ):
79+ raise ValueError (f"Expected dictionary at path '{ service_env_path } '" )
80+ return {key : expand_env_placeholders (value ) if isinstance (value , str ) else value for key , value in env_config .items ()}
81+
82+
83+ def load_yaml_config (user_config_file : Optional [str ] = None , default_env : str = "PRODUCTION" ) -> Dict [str , Any ]:
84+ """Return the active config dict (for backward compatibility)"""
85+ return get_active_config (user_config_file , default_env )
86+
87+
88+ def get_yaml_value (
89+ config : Dict [str , Any ],
90+ path : str ,
91+ cache : Optional [Dict [str , Any ]] = None
92+ ) -> Any :
93+ """Alias for get_config_value() for backward compatibility"""
94+ return get_config_value (config , path , cache )
95+
96+
97+ def unflatten_config (flattened_config : dict ) -> dict :
98+ """Convert flattened config back to nested structure"""
99+ nested = {}
100+ for key , value in flattened_config .items ():
101+ keys = key .split ("." )
102+ current = nested
103+ for k in keys [:- 1 ]:
104+ if k not in current :
105+ current [k ] = {}
106+ current = current [k ]
107+ current [keys [- 1 ]] = value
108+ return nested
97109
98110
99111def expand_env_placeholders (value : str ) -> str :
100- # Expand environment placeholders in the form ${ENV_VAR:-default}
112+ """ Expand environment placeholders in the form ${ENV_VAR:-default}"""
101113 # Supports nested expansions like ${VAR1:-${VAR2:-default}}
102114 pattern = re .compile (r"\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?}" )
103115 max_iterations = 10 # Prevent infinite loops
@@ -116,6 +128,7 @@ def replacer(match):
116128 return value
117129
118130
131+ # Config path constants
119132VIEW_ENV_FILE = "services.view.env.VIEW_ENV_FILE"
120133API_ENV_FILE = "services.api.env.API_ENV_FILE"
121134DEFAULT_REPO = "clone.repo"
0 commit comments