pymongo and attribute dictionaries

Steven D'Aprano steve+comp.lang.python at pearwood.info
Wed Feb 4 17:38:26 EST 2015


Travis Griggs wrote:

> I really like pymongo. And I really like Python. But one thing my fingers
> really get tired of typing is
> 
> someDoc[‘_’id’]

I've never used pymongo, so can't comment from experience, but surely any
library which encourages, if not requires, that you access private data _id
doesn't inspire me with confidence.

Also, the above is a syntax error, but you probably know that.


> This just does not roll of the fingers well. Too many “reach for modifier
> keys” in a row.

*One* modifier key in a row is too many? 

s o m e SHIFT D o c [ ' SHIFT _ i d ' ]


You can cut that by 50% by changing your naming convention:

somedoc['_id']

While you're at it, you can cut the total number of keystrokes too:

doc['_id']


If there are a fixed set of key names that you use repeatedly, you can do
this:


ID, FE, FI, FO, FUM = '_id', '_fe', '_fi', '_fo', '_fum'
# later
someDoc[ID]


Ah, but that needs the shiftkey... well, I guess that PEP 8 naming
conventions aren't compulsory, and so long as you don't need the built-in
`id` function, it's okay to shadow it (consenting adults and all that), so:

id, fe, fi, fo, fum = '_id', '_fe', '_fi', '_fo', '_fum'
# later
someDoc[id]


> I would rather use
> someDoc._id

That has exactly the same number of modifier keys as your original example.
I'm starting to sense that you don't actually care about the SHIFT key...


[...]
> The problem I have is not how to do the AttributeDictionary subclass,
> there are plenty of those examples. The problem is that the pymongo APIs
> already return dictionaries. In a language (Smalltalk, Objective-C, Ruby)
> that supports class extensions, that would be my first tool of choice to
> solve this problem. I’d just extend Dictionary to behave the way I want
> and be done with it. I can’t do that in Python though. I guess I could
> make my own module that subclasses the relevant pymongo classes, and do
> super() calling implementations of all of the relevant methods, coercing
> the return type. That is a maintenance headache though.
> 
> What are my options, if any?

HingTFU comes to mind :-)

But as "an academic exercise" *nudge nudge, wink wink* there's always the
Adaptor design pattern. Import as much or as little of pymongo as you need,
wrap it in an adaptor, and use that. There are lots of different ways that
you could do this, some more monkey-patchy than others.


# pymongo adaptor module (untested)
from pymongo import *

import functools

def wrap(name):
    """Wrap function called `name` so it returns an AttrDict instead 
    of dict. May the gods have mercy on your soul."""
    func = globals()[name]
    @functools.wraps(func)
    def inner(*args, **kwargs):
        result = func(*args, **kwargs)
        if isinstance(result, dict):
            result = AttrDict(result)
        return result
    globals()[name] = func


FUNCTIONS_THAT_RETURN_DICTS = ['this', 'that', 'another']
for name in FUNCTIONS_THAT_RETURN_DICTS:
    wrap(name)



Extending this to use introspection to automatically detect the pymongo
functions instead of having to list them manually is left as an exercise.
(Hint: import pymongo; for obj in vars(pymongo): ...).

Extending this to wrap methods of classes is also left as an exercise.
(Hint: don't subclass. Search the ActiveState Python recipes for "automatic
delegation" by Alex Martelli.)

And now just use the adaptor module instead of the original.


-- 
Steven




More information about the Python-list mailing list