Key

Key helps you explore python objects quickly, especially in an interpreter.

It’s a library I wrote for our python database shell at work and recently managed to clean it up and release it.

pip install key

Since it’s built for the interpreter, I’ve tried hard to make it as few keystrokes as possible to get useful stuff done.

So to get started:

import k

The k module you’ve just imported is actually an object 😲 ! Don’t worry, Guido says it’s fine.

And it’s a pretty “magic” object at that. So accessing any attribute foo of k will return a “foo getter”. You can think of it as a try-hard version of operator.itemgetter('foo').

k.foo(obj)  # equivalent to obj.foo

Almost equivalent anyway. It’ll swallow any AttributeErrors and return None instead, so it’s a “safe” getter in that sense. But still not very useful. So you can chain it!

k.foo.bar(obj)  # obj.foo.bar

Now we’re getting somewhere! Without k, if you wanted “safe” behavior, you’d have to do something like..

if hasattr(obj, 'foo') and hasattr(obj.foo, 'bar'):
  return obj.foo.bar

No fun. Again, this is relevant especially in a database shell—object relationships may come back None, and it’s common to access deeply nested relationships. It’s also common to project over lists (this was actually the original motivating use case).

k.foo(objects)  #  equivalent to [obj.foo for obj in objs]
k.foo.bar(objects)  #  equivalent to [obj.foo.bar for obj in objs]

More useful. But it’s also common to want to view a few fields of a list of objects, so you can combine getters with +.

(k.first_name + k.last_name)(users)  
# [{'first_name':.., 'last_name':..},..]

and of course, these work together nicely, so:

(k.coach.first_name + k.first_name)(users)  
# [{"coach_first_name":.., "first_name":..},..]

If you don’t want None as your default, you can provide another:

k.foo(obj, default="HI")

It will work with object attributes just the same as dictionary keys, too.

k.some.nested.json.value(json)  # json["some"]["nested"]..

If you have lists of lists, you can flatten them:

k.messages(users)  
# [[message1, message2..], [message3, message4..]..]
k.messages(users, flatten=True)  
#  [message1, message2, message3, message4]
k.messages(flatten=True).created_at(users)  
# [date1, date2, date3, date4]

Now we’re getting somewhere!

Sometimes it’s useful to return yourself, or print every attribute you have:

k._(obj) #  obj
k.__(obj) #  obj.__dict__

Anyway, check it out! It was fun to write, and I use it almost every day at work. Let me know if you find it useful, too.