Created
December 20, 2018 22:54
-
-
Save josiah14/c7ad1887de5d3f93908952774ff9770f to your computer and use it in GitHub Desktop.
Functional Dependency Injection In Python (Inspired by the Reader Monad)
This file contains 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
class Reader(object): | |
pass | |
def to_camel_case(snake_str): | |
parts = snake_str.split('_') | |
return parts[0] + ''.join([s.capitalize() for s in parts[1:]]) | |
def strip_leading_underscores(string): | |
if string.startswith('_'): | |
string = string[1:] | |
string = string[1:] if string.startswith('_') else string | |
return string | |
def inject_dependency_kwarg(dependency_name, dependency_value, *args): | |
new_reader_name = \ | |
Reader.__name__ + to_camel_case(dependency_name).capitalize() | |
new_reader = type(new_reader_name, (Reader,), {})() | |
new_reader.__setattr__('__name__', new_reader_name) | |
for func in args: | |
func_name = strip_leading_underscores(func.__name__) | |
partial_func = partial(func, **{dependency_name: dependency_value}) | |
partial_func.__setattr__('__name__', func_name) | |
new_reader.__setattr__(func_name, partial_func) | |
return new_reader | |
def inject_dependency_first_arg(dependency_name, dependency_value, *args): | |
new_reader_name = \ | |
Reader.__name__ + to_camel_case(dependency_name).capitalize() | |
new_reader = type(new_reader_name, (Reader,), {})() | |
new_reader.__setattr__('__name__', new_reader_name) | |
for func in args: | |
func_name = strip_leading_underscores(func.__name__) | |
partial_func = partial(func, dependency_value) | |
partial_func.__setattr__('__name__', func_name) | |
new_reader.__setattr__(func_name, partial_func) | |
return new_reader | |
def import_public_attributes(obj, parent_frame_locals): | |
""" | |
Imports the public attributes of the provided object so that they | |
can be used without referencing the object from the parent frame (the caller). | |
:param obj: The object whose public attributes to import | |
:param parant_frame_locals: The dictionary returned from locals() in the | |
function call. | |
Example: | |
In [118]: def add(*args, logger=None): | |
...: logger.info('adding %s' % str(args)) | |
...: return reduce(lambda acc, x: acc + x, args) | |
...: | |
In [119]: logger_reader = inject_dependency_kwarg('logger', logger, add) | |
In [120]: logger_reader.add(1,2,3) | |
2018/12/18 14:25:42 adding (1, 2, 3) | |
Out[120]: 6 | |
In [121]: logger_reader.add.__name__ | |
Out[121]: 'add' | |
In [122]: import_public_attributes(logger_reader, locals()) | |
In [123]: add | |
Out[123]: functools.partial(<function add at 0x127a47840>, logger=<logging.Logger object at 0x1279fc160>) | |
In [125]: add(1,2,3) | |
2018/12/18 16:58:39 adding (1, 2, 3) | |
Out[125]: 6 | |
In [126]: one_reader = inject_dependency_first_arg('one', 1, logger_reader.add) | |
In [128]: one_reader.add(2,3) | |
2018/12/18 17:00:38 adding (1, 2, 3) | |
Out[128]: 6 | |
In [129]: import_public_attributes(one_reader, locals()) | |
In [130]: add(2,3) | |
2018/12/18 17:00:59 adding (1, 2, 3) | |
Out[130]: 6 | |
""" | |
public_attrs = [attr for attr in dir(obj) if not attr.startswith('_')] | |
for attr in public_attrs: | |
parent_frame_locals[attr] = obj.__getattribute__(attr) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO: make it so that you don't have to pass in
locals()
, but rather, the function extracts thelocals()
out of the caller-frame itself. May or may not be possible...