Skip to content

Instantly share code, notes, and snippets.

@josiah14
Created December 20, 2018 22:54
Show Gist options
  • Save josiah14/c7ad1887de5d3f93908952774ff9770f to your computer and use it in GitHub Desktop.
Save josiah14/c7ad1887de5d3f93908952774ff9770f to your computer and use it in GitHub Desktop.
Functional Dependency Injection In Python (Inspired by the Reader Monad)
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)
@josiah14
Copy link
Author

In [58]: def print_things():
    ...:     call_frame = sys._getframe(1)
    ...:     print(call_frame.f_locals)
    ...:     print(dir(call_frame))
    ...:

In [60]: def add(x, y, *args, z=None, **kwargs):
    ...:     print_things()
    ...:     from functools import reduce
    ...:     return x + y + (z if z else 0) + reduce(lambda b, acc: b + acc, list(args).extend(kwargs.values()) or [0])
    ...:
    ...:

In [63]: add(1,2,3,4,5,z=6,w=10)
{'kwargs': {'w': 10}, 'x': 1, 'z': 6, 'args': (3, 4, 5), 'y': 2}
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'f_back', 'f_builtins', 'f_code', 'f_globals', 'f_lasti', 'f_lineno', 'f_locals', 'f_trace']

@josiah14
Copy link
Author

TODO: make it so that you don't have to pass in locals(), but rather, the function extracts the locals() out of the caller-frame itself. May or may not be possible...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment