1+ import os
2+
3+ import yaml
4+
5+
6+ class Config :
7+ """
8+ A class to handle configuration settings loaded from a YAML file.
9+ """
10+ _instance = None
11+
12+ def __new__ (cls , config_path = "config.yaml" ):
13+ if cls ._instance is None :
14+ cls ._instance = super (Config , cls ).__new__ (cls )
15+ cls ._instance ._initialize (config_path )
16+ return cls ._instance
17+
18+ def _initialize (self , config_path ):
19+ if not os .path .exists (config_path ):
20+ raise FileNotFoundError (f"Configuration file not found at { config_path } " )
21+
22+ try :
23+ with open (config_path , 'r' ) as file :
24+ self ._settings = yaml .safe_load (file )
25+ except yaml .YAMLError as e :
26+ raise ValueError (f"Error parsing YAML file: { e } " )
27+
28+ def __getattr__ (self , name ):
29+ """
30+ Allows accessing settings like attributes (e.g., config.database.host).
31+ """
32+ if name in self ._settings :
33+ value = self ._settings [name ]
34+ if isinstance (value , dict ):
35+ # Recursively wrap nested dictionaries
36+ return _DictWrapper (value )
37+ return value
38+
39+ # Fallback to the default __getattr__ behavior
40+ return super ().__getattr__ (name )
41+
42+ def get (self , key , default = None ):
43+ """
44+ Provides a safe way to get a value with an optional default,
45+ similar to dictionary's .get() method.
46+ """
47+ keys = key .split ('.' )
48+ current_dict = self ._settings
49+ for k in keys :
50+ if isinstance (current_dict , dict ) and k in current_dict :
51+ current_dict = current_dict [k ]
52+ else :
53+ return default
54+ return current_dict
55+
56+ def get_int (self , key , default = 0 ):
57+ """
58+ Provides a safe way to get a value with an optional default,
59+ similar to dictionary's .get() method.
60+ """
61+ keys = key .split ('.' )
62+ current_dict = self ._settings
63+ for k in keys :
64+ if isinstance (current_dict , dict ) and k in current_dict :
65+ current_dict = current_dict [k ]
66+ else :
67+ return default
68+ return current_dict
69+
70+ def get_bool (self , key , default = False ):
71+ """
72+ Provides a safe way to get a value with an optional default,
73+ similar to dictionary's .get() method.
74+ """
75+ keys = key .split ('.' )
76+ current_dict = self ._settings
77+ for k in keys :
78+ if isinstance (current_dict , dict ) and k in current_dict :
79+ current_dict = current_dict [k ]
80+ else :
81+ return default
82+ if type (current_dict ) is str :
83+ current_dict = eval (current_dict )
84+ return bool (current_dict )
85+
86+
87+ class _DictWrapper :
88+ """
89+ Helper class to enable attribute-style access for nested dictionaries.
90+ """
91+ def __init__ (self , data ):
92+ self ._data = data
93+
94+ def __getattr__ (self , name ):
95+ if name in self ._data :
96+ value = self ._data [name ]
97+ if isinstance (value , dict ):
98+ return _DictWrapper (value )
99+ return value
100+ raise AttributeError (f"'{ self .__class__ .__name__ } ' has no attribute '{ name } '" )
101+
102+ # Example Usage:
103+ if __name__ == "__main__" :
104+ # Create a dummy config.yaml file for the example
105+ sample_config_content = """
106+ tvdb:
107+ apikey: "bed9264b-82e9-486b-af01-1bb201bcb595" # Enter TMDb API Key (REQUIRED)
108+
109+ omdb:
110+ apikey: "9e62df51" # Enter OMDb API Key (Optional)
111+ """
112+ with open ("config.yaml" , "w" ) as f :
113+ f .write (sample_config_content )
114+
115+ try :
116+ config = Config ()
117+
118+ print ("--- Attribute Access ---" )
119+ print (f"tvdb key: { config .tvdb .apikey } " )
120+ print (f"omdb key: { config .omdb .apikey } " )
121+
122+ print ("\n --- 'get' Method Access ---" )
123+ print (f"tvdb key: { config .get ('tvdb.apikey' )} " )
124+ print (f"Default Value Test: { config .get ('omdb.sproing' , 'default_value' )} " )
125+
126+ except (FileNotFoundError , ValueError ) as e :
127+ print (f"An error occurred: { e } " )
128+ finally :
129+ # Clean up the dummy file
130+ if os .path .exists ("config.yaml" ):
131+ os .remove ("config.yaml" )
0 commit comments