I've been playing with this, why not make the container class a subclass of
dict
with a custom attribute access? This makes the interface of the container more friendly and one can use dictionary operations to update it.
from dataclasses import dataclass
@dataclass
class Inject:
name: str = None
class Wirer(dict):
def __setattr__(self, name, bean):
self[name] = bean
def __getattr__(self, name):
if name in self:
b = self[name]
# must do this first in case of mutual dependencies
# also avoids further calls to __getattr__
object.__setattr__(self, name, b)
self._wire_bean(name, b)
return b
else:
raise AttributeError(name)
def _wire_bean(self, name, b):
if not hasattr(b, "__dict__"):
# built-in object such as a string
return b
attrs = vars(b)
for n, v in attrs.items():
if isinstance(v, Inject):
dep_name = v.name or n
attrs[n] = getattr(self, dep_name)
if __name__ == '__main__':
class ProductDb:
def __init__(self):
self.db_path=Inject("database_path") # specify the bean name
def get_products(self):
print(f"getting products from {self.db_path}")
di = Wirer()
di.pdb = ProductDb()
di.database_path = "c:/Users/kent/test.db"
di.pdb.get_products()
di = Wirer(pdb=ProductDb(), database_path='c:/spam/spam.db')
di.pdb.get_products()
di.pdb.get_products()
Output:
getting products from c:/Users/kent/test.db
getting products from c:/spam/spam.db