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.

Feedback for the JIRA team

I got JIRA’s automated customer satisfaction quiz today. I got carried away with my response, and I thought I’d share it. Out of seven, I called Jira’s “ease of use” 1. I’ve cleaned it up (slightly) slightly for wider viewing.

Jira’s fundamental flaw is its awkward user experience. It gives you enormous power to customize your workflow, but all in the form of discrete, non-uniform and definitely not orthogonal tools. Each customization tool needs to be discovered/found and learned separately – except for a few (very good!) shared notions like JQL you have to teach them to yourself from scratch. The mass-edit stories flow is a great example: it in no way relates to the rest of your interactions with the board, it’s just a bunch of menus. I mean seriously, you guys are one of the biggest names in Software Development right now. You’re making professional tools, but that does not mean they have to be a drag to use! Overall the interaction with each bit of Jira feels independently evolved rather than designed. I’m not saying that you should replace every flow, but as you add new ones (and you are adding new ones – Jira is totally different now from when I first used it in about 2015, and it’s much better!) try to have a uniformity so that knowledge of how to use one can transfer to the others.

Unlike, say, Grafana, where you can save, load, share, version-control and ask intelligent Stack Overflow questions about your graphs because they are actually saved in text form, Jira is entirely (as far as I can tell) UI driven and Database-backed. It’s very hard to google how to do things or find instructions because the things you’re looking for aren’t always labeled, or are very small text somewhere hiding in a menu. Having ‘source code’ for all customizable features (and I don’t mean writing extensions, I mean, for example, the configuration of our board or card layout!) even if it was reams and reams of gross XML, would be preferable to the current state of affairs. JIRA’s customization is its strong suit, but these customizations are difficult to share and communicate even with other teams within our organization, to say nothing of finding good tutorials.

At one point I was like “I want to make a new graph” so I go to the graphs page. No “new” button. I want to add a custom filter, so I go to the custom filters and didn’t find a new button. I’ll admit – I’m a total neanderthal when it comes to modern web UX. However, it seems to me that if you’d like to change or add a thing, the option to do it should be right next to the existing things. This principle applies to the backlog view, for example – if I want to create a story, I can click on the end of the backlog. Or inside a sprint. Or also the “+” icon which is inexplicably located in the navigation menu on the left. If you need a video to communicate how to use a thing on a computer, the thing isn’t easy enough. I would suggest that to broaden your reach, you should do most of your UX testing with people who haven’t become acclimated to Jira’s way of doing things. Jira is 100x more powerful than Trello, so why do I still see people using Trello? Because although you can’t customize Trello to do everything, everything it does do, it does fine.

Don’t just be Pivotal with more customization or Trello with more features. There should be a right way to accomplish things, the right way should be obvious, and it should be easy to communicate what the right way is to others, or apply that right way to other things. I’m counting on you fine folks.

Python list comprehensions

If, like me, you learned python and programming at the same time, you may have missed out on advanced features that, while awesome in python, won’t carry over to other languages you’ll “graduate” to using. One of these features is a list comprehension. It lets you in a compact (and readable) way write loops that take a list and return a list, and does not require lambda syntax. They let you write this:

list_squared = []
for x in [1, 2, 3, 4, 5]:
    list_squared.append(x ** 2)

like this:

[x ** 2 for x in [1, 2, 3, 4, 5]]

This really shines when you’re transforming and extracting data – you can also stick an ‘if’ at the end, turning this:

odds_squared = []
for x in [1, 2, 3, 4, 5]:
    if x % 2:
        odds_squared.append(x ** 2)

into this

[x ** 2 for x in [1, 2, 3, 4, 5] if x % 2]

It might not be a huge improvement in amount of code (or LOC, since you’ll want to break complex list comprehensions across multiple lines) but it saves you from potential mistakes, typing ‘append’, and (importantly) having to declare and use another variable name. I consider that a win.

If you found this post exciting, generator expressions will blow your mind.