Created
October 27, 2015 07:30
-
-
Save zet4/fa9133f301ff1b9d4de4 to your computer and use it in GitHub Desktop.
Inversion of Control in Python (DI)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
class FeatureBroker: | |
def __init__(self, allowReplace=False): | |
self.providers = {} | |
self.allowReplace = allowReplace | |
def Provide(self, feature, provider, *args, **kwargs): | |
if not self.allowReplace: | |
assert feature not in self.providers, \ | |
"Duplicate feature: {}".format(feature) | |
if callable(provider): | |
def call(): return provider(*args, **kwargs) | |
else: | |
def call(): return provider | |
self.providers[feature] = call | |
def __getitem__(self, feature): | |
try: | |
provider = self.providers[feature] | |
except KeyError: | |
raise KeyError("Unknown feature named {}".format(feature)) | |
return provider() | |
features = FeatureBroker() | |
def NoAssertion(obj): return True | |
def IsInstanceOf(*classes): | |
def test(obj): return isinstance(obj, classes) | |
return test | |
def HasAttributes(*attributes): | |
def test(obj): | |
for each in attributes: | |
if not hasattr(obj, each): | |
return False | |
return True | |
return test | |
def HasMethods(*methods): | |
def test(obj): | |
for each in methods: | |
try: | |
attr = getattr(obj, each) | |
except AttributeError: | |
return False | |
if not callable(attr): | |
return False | |
return True | |
return test | |
class RequiredFeature(object): | |
def __init__(self, feature, assertion=NoAssertion): | |
self.feature = feature | |
self.assertion = assertion | |
def __get__(self, obj, T): | |
return self.result # <-- will request the feature upon first call | |
def __getattr__(self, name): | |
assert name == 'result', \ | |
"Unexpected attribute request other then 'result'" | |
self.result = self.Request() | |
return self.result | |
def Request(self): | |
obj = features[self.feature] | |
assert self.assertion(obj), \ | |
"The value %r of %r does not match the specified criteria" \ | |
% (obj, self.feature) | |
return obj | |
class Component(object): | |
"Symbolic base class for components" | |
class Bar(Component): | |
con = RequiredFeature('Console', HasMethods('WriteLine')) | |
title = RequiredFeature('AppTitle', IsInstanceOf(str)) | |
user = RequiredFeature('CurrentUser', IsInstanceOf(str)) | |
def __init__(self): | |
self.X = 0 | |
def PrintYourself(self): | |
self.con.WriteLine('-- Bar instance --') | |
self.con.WriteLine('Title: %s' % self.title) | |
self.con.WriteLine('User: %s' % self.user) | |
self.con.WriteLine('X: %d' % self.X) | |
class SimpleConsole(Component): | |
def WriteLine(self, s): | |
print(s) | |
class BetterConsole(Component): | |
def __init__(self, prefix=''): | |
self.prefix = prefix | |
def WriteLine(self, s): | |
lines = s.split('\n') | |
for line in lines: | |
if line: | |
print(self.prefix, line) | |
else: | |
print('') | |
def GetCurrentUser(): | |
return os.getenv('USERNAME') or 'Some User' | |
if __name__ == '__main__': | |
print('\n*** IoC Demo ***') | |
features.Provide('AppTitle', 'Inversion of Control ...\n\nThe Python Way!') | |
features.Provide('CurrentUser', GetCurrentUser) | |
features.Provide('Console', BetterConsole, prefix='-->') | |
# features.Provide('Console', BetterConsole(prefix='-->')) | |
bar = Bar() | |
bar.PrintYourself() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment