blaix / woma

A python web development framework
MIT License
5 stars 0 forks source link

Injectable actions #28

Open blaix opened 6 years ago

blaix commented 6 years ago

class SayHello:
  def __call__(self, name):
    return 'Hello, {}!'.format(name)

def say_hello_handler(
    request: woma.Request,
    response: woma.Response,
    say_hello: SayHello):
  name = request.data['name']
  greeting = say_hello(name)
  response.write(greeting)
  return response

If you call the handler yourself (e.g. in tests), you are responsible for passing all arguments. When woma calls your handler (to handle a real request), it will instantiate an instance of the action's type and pass that. The action(s) can be any number of kwargs, named anything other than request and response.

To do this, I should create a new library for doing dependency injection this way. It should look something like this:

class Inner:
  def __init__(self):
    self.forty_two = 42

class Outer:
  def __init__(self, inner: Inner):
    self.forty_two = inner.forty_two

import injector
outer = injector.get(Outer)
outer.forty_two  # => 42

# calling Outer normally requires manual passing
# I very much do NOT want to magically inject on normal calls
# this will avoid "accidental integration"
# (implicitly using something with side effects, esp. in tests)
outer = Outer()  # => TypeError, missing argument
outer = Outer(inner=Inner())
outer.forty_two  # => 42

Internally, woma would use injector to instantiate actions. Also, kwargs should be injected arbitrarily deep. For example:

class AllTheWayDown:
  ...

class Inner:
  def __init__(self, turtles: AllTheWayDown):
    ...

class Outer:
  def __init__(self, inner: Inner):
    ...

outer = injector.get(Outer)  # instantiates Inner for Outer
                             # and AllTheWayDown for Inner

This means actions can themselves depend on other actions, boundaries, etc. that can also depend on others, etc. and they will all be instantiated by the injector. The fact that they must be instantiated manually outside of a woma-handled request (e.g. in tests) will discourage a nesting level that is too deep.

Things I do not like about this approach:

blaix commented 6 years ago

https://github.com/alecthomas/injector provides functionality very similar to what I'm describing, but I'm worried that it does too much, and I don't like the decorators. I want to emphasize that these are just normal parameters. In the case of a normal request, woma passes the parameters in, but any other time (e.g. in tests) you must pass them. I don't want to focus on injectors and providers and implementers and containers at all.

blaix commented 6 years ago

worth looking at how pyramid-services works. Instead of literally instantiating the type in the hint, you could register objects, or factories that return objects, for a given interface, and the injector can give you the instance for the interface used as a type hint (or by passing it to some method on a container object, ala request.find_service in pyramid-services).