Dependency Injection (like pytest fixtures) with a Python decorator

If you’ve used Pytest, you’ll notice a neat little pattern. If you want a particular fixture, you just ask for it, seriously, check out the docs:

@pytest.fixture
def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]


def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)

test_fruit_salad gets called when you run tests, and it will automatically pass the result of fruit bowl to test_fruit_salad. This pretty much blew my mind when I first saw it.

The following decorator helps you achieve something similar with your own functions. It lets you do something like this.

@opt_in_args
def request_a(foo: str, *, a: int):
    print(f"Foo is {foo}, a is {a}")

@opt_in_args
def request_b(*, b: str):
    print(f"b is: {b}")

@opt_in_args
def request_both(*, b: str, a: int):
    print(f"b is: {b}, a is: {a}")

request_a("Blarg")
request_b()
request_both()

Note that the * args just mean “everything after this is a keyword argument only”, it helps guard against accidentally passing in bad kwargs and you should always use it, but the above program will work the same without it. The output of the program looks like this:

Foo is Blarg, a is 123
b is: foobar
b is: foobar, a is: 123

First let’s look at the implementation of the decorator:

import inspect

def opt_in_args(func):
    def wrapper(*args, **kwargs):
        signature = inspect.signature(func)
        opted_in = {}
        optional_kwargs = {
            "a": 123,
            "b": "foobar",
        }
        for kwarg, value in optional_kwargs.items():
            if kwarg in signature.parameters:
                assert signature.parameters[kwarg].annotation == type(value)
                opted_in[kwarg] = value
        return func(*args, **kwargs, **opted_in)
    return wrapper

Let’s look at a few specific lines to illuminate what it’s doing:

def opt_in_args(func):
    def wrapper(*args, **kwargs):

This is standard decorator magic. The outer function has the function in-scope, the wrapper is (because we return it from the decorator) what actually gets called in place of the wrapped function, so we capture *args and *kwargs so we can pass them along.

signature = inspect.signature(func)

Note that we imported inspect at the top of the module. Inspect lets you introspect on python functions, and in this case we use it to grab the signature. signature.paremeters will now have a key for each parameter the function accepts; we can now write any code we like based on the params.

        optional_kwargs = {
            "a": 123,
            "b": "foobar",
        }
        for kwarg, value in optional_kwargs.items():
            if kwarg in signature.parameters:

This approach is totally optional. There are any number of ways you can enumerate the optional kwargs. You can make another decorator and supply functions which are only called if they’re requested (I think pytest fixtures must be doing something like this) or you can have a bunch of ifs to decide what to instantiate and put in.

                assert signature.parameters[kwarg].annotation == type(value)

This I just put in there to show one way you could enforce typing on the keyword arguments. This is of course also optional; you could write your decorator to only care about the name by removing this line.

                opted_in[kwarg] = value
        return func(*args, **kwargs, **opted_in)

We create a dictionary called opted_in and populate it with the additional kwargs we want to pass. The final line of the wrapper function calls the wrapped function with the args and kwargs the caller sent, unmodified, and finally with the opted_in args the function asked for in its signature.

For such a slick effect, this is a surprisingly easy decorator; decorator magic often is.

San Francisco

“Enjoying ‘walks’ is sort of a cliche. I consider myself an avid pedestrian, but while technically relaxing I also see walking an act of impatience. I like bloody-minded trudges from point A to point B when other means of transport would require waiting. So to experience the famous Golden Gate, I figured the most appropriate way was to walk from one end to the other.

Photograph of the Golden Gate Bridge from the San Francisco side. The bridge takes up about half of the frame, the right side is all bay. SF Viaduct visible in the foreground.

The bridge itself was massive. Better writers than I have described its grandeur. It’s probably the biggest single thing I’ve encountered. It felt as solid as stone gazing at its cable stays, the tension invisible, but you could feel the legendary elasticity of the steel structure underfoot when vibrations from traffic rippled across its surface.

Photo: Golden gate bridge tower and main cables, from the bridge.
Photo: San Fransisco skyline and bay, from the bridge

Do I recommend crossing it on foot? Well, it’s a bit loud. The views of the city, bay, and mountains are very nice. The constant reminders that people jump off the thing were a bit spooky. The traffic tended to stay below 80db but a bit louder as vehicles hit rough patches (joints?) in the road. I wouldn’t recommend earplugs, because you’re going to need your situational awareness to avoid blocking bikes.

Photo: Presidio from Golden Gate Bridge

About halfway across, I spied a lookout on a mountain high above the roadway. ‘I am gonna climb that‘ I said to myself. As I got further, my resolve strengthened; the view was bound to be amazing.

Photo: Hill with battery spencer from bridge

I didn’t spend much time at vista point-I was eager to get to whatever path lead up that hill, which turns out to be the former home of Battery Spencer. There was a nice shortcut to avoid the road and see some wildflowers.

Battery Spencer had some great views of the bridge and the headlands, but I couldn’t help but once again gaze up! Roads cut into the sides of these mountains invited me to climb higher. Once again, I elected to press on.

The views of the mountains sweeping down into Kirby Cove were breathtaking. I really lucked out on the weather. There was a rough path beside the road. The grade was merciful, but you couldn’t escape the feeling of being at the top of a very long fall. The roadcut had changed the erosion timeline for these hills, and every inch seemed to threaten imminent rockslide. Thrilling, to be sure.

I’d told myself ‘ah, I can make it to the observation deck’ but once I got there, again, I saw a higher peak: there was a great lookout (and more treacherous road) at what turned out to be Hawk Hill.

I was starting to feel the climb then; I regretted not taking any water with me, that would have been smart. But as unplanned hikes go, at least this one was done on a full stomach. I salute the bikers who were passing me on the way up-this must have been a tough climb. The reward on the way down, I imagine, is probably worth it.

Hawk Hill turns out to be not just one observation deck with views of the city, but also an unfinished battery ‘129’ with tunnels you can enter – fans of STALKER or Fallout will enjoy the ruins here.

The very top was only a short climb away, and the view was well worth the hike. Walking the headlands was a much more pleasant experience than the bridge with its noise. Somehow the actual danger of those sheer cliffs was more tolerable than the much safer bridge plastered with memento mori.

I will admit, dear reader, that when I reached the summit of Hawk Hill I felt ready to call it quits and take a Lyft back to civilization. It was not to be, however, so I descended back to Vista Point once again on my own steam.

After recovering in Sausalito and taking the ferry back (a trip I highly recommend even if you skip the hiking) and enjoying the ferry terminal, I took a slow (recovery?) stroll through the city (go to City Lights if you can) before one more time being seized by the desire to walk up a hill.

Telegraph hill turned out to be the toughest climb of the day. I went up Filbert Street, which attacks the hill head on. After a day of lazily winding roadcuts, it was brusque to say the least.

You’ll just have to take my word on the last bit, I’d burned out my phone battery so no pictures exist!