Django: Duck typing-friendly way of accepting record or slug of record for input

Go To StackoverFlow.com

1

I have a function like this:

def foo(bar):
    ...

Now bar can either be a Django record or the slug pointing to a record. I need this flexibility so that I can call this function when I have the actual record at hand or am calling this from a more generic function that only has bar available as a string (pulled from a database).

I realize I can do something like:

def foo(bar):
    try:
        bar.pk
    except AttributeError:
        bar = Bar.objects.get(slug=bar)
    ...

But this doesn't seem elegant at all.

If I can I'd like to avoid using isinstance.

2012-04-03 22:00
by Jordan Reiter
If I can I'd like to avoid using isinstance - Any particular reason - MattH 2012-04-03 22:04
Why don't you create two different functions. A new one that's foo_with_slug that does the lookup and then calls your foo method - Sam Dolan 2012-04-03 22:05
Force of habit, I suppose? I really only like to use it when my function would actually be fooled by duck typing, for example if it can take strings or arrays of strings - Jordan Reiter 2012-04-03 22:06
@sdolan I guess that might be the easiest way, actually. It would be a lot easier if I didn't have to keep track of two different functions to target - Jordan Reiter 2012-04-03 22:08
So, to recap, you want to avoid isinstance because you like avoiding isinstance... just to be clear - MattH 2012-04-03 22:08
@MattH isinstance is bad form if only because then it requires an inheritance tree as opposed to simply requiring an attribute. It is FAR less ducky than his current solution - cwallenpoole 2012-04-03 22:09
@JordanReiter: You're writing a function to accept a django record or a slug to a record and you're upset that isinstance would preclude it accepting something that looked a little like a django record - MattH 2012-04-03 22:12
@MattH I never said I was upset, but my point holds -- it still is not duck- - cwallenpoole 2012-04-03 22:15
@MattH, as I said I am looking for a duck typing solution to this issue if one exists. If it doesn't then I suppose isinstance is definitely an alternative - Jordan Reiter 2012-04-03 22:15
If you want to duck type a django record in a function, the I guess the sound approach would be to check it for all of the django record methods you plan on using in the function - MattH 2012-04-03 22:19
In this case bar is being saved as a foreign key to another record (i.e. Foo.object.create(name=..., bar=bar) - Jordan Reiter 2012-04-03 22:21


5

You are by definition not using Duck Typing. Duck Typing says that "if it talks like a duck and looks like a duck, it's a duck."

Duck Typing means you can pass two objects of completely different classes to the method and have it work because they both implement the same methods/attributes (or handles missing ones gracefully). This means that the method never cares about about what type it gets, just that whatever object you pass it has the attributes it expects to use.

In your case you want to pass an object sometimes and a string that can be used to look up said object other times. This has nothing to do with Duck Typing.

isinstance is the right way to solve this. In this case this is the clearest way to solve the problem, anything else is going to be more complicated, harder to understand have 0 benefits. You could use try/except on an attribute or hasattr, but that's likely to leave any future developers more confused than anything else. Duck Typing is great it replaces casting various subclasses around to match some particular function, but duck typing doesn't apply in this case.

In short. Just use isinstance. For your case it's the right (pythonic) way to do it.

2012-04-03 23:05
by John
Just checked django.db.models.fields.related, it uses isinstance for validating foreign key assignments. Thank you John for explaining Duck Typing in this way, it will help me differentiate the kinds of isinstance-avoiders. I was struggling to see why it would be beneficial to this situation and you've cleared that up - MattH 2012-04-04 10:23
@MattH Agreed. I've just run into so many "isinstance considered harmful" (http://www.canonical.org/~kragen/isinstance/) type articles that I assumed that the use of isinstance is practically verboten in pythonic programming - Jordan Reiter 2012-04-04 13:35
@JordanReiter: That article explains some of the isinstance hatred I've seen. I can see the point, but I think this line from the article should be emphasized: Sometimes, of course, violating this promise is worth the payoffs --- isinstance, like goto, is not pure evil. But it is a trap for new programmers. Beware! Don't use isinstance unless you know what you're doing. It can make your code non-extensible and break it in strange ways down the line.MattH 2012-04-04 14:01
The problem is people tend to fall back on isinstance in ways that break the flexible type system. For example if your method accepts an iterable, you shouldn't care if that iterable is a queryset, a list, a tuple, or anything else. You should just care that it's iterable. However sometimes you really do need to tell the difference (like between a string and a model instance - John 2012-04-04 19:03


1

I'm not sure that that is a terrible way of handling that, but if I wanted to do something similar, I would probably use hasattr:

def foo(bar):
    if hasattr(bar,"pk"):
        bar.pk
    else:
        # I include the str in case some other object with a __str__ happens
        # to come through.
        bar = Bar.objects.get(slug=str(bar))
2012-04-03 22:07
by cwallenpoole
What happened to unicode support? : - MattH 2012-04-03 22:09
@MattH I suppose that this could handle unicode bette - cwallenpoole 2012-04-03 22:13
Unless I'm missing something, you could just substitute str(bar) with unicode(bar) and get the same result along with unicode support - Jordan Reiter 2012-04-03 22:18
as far as I know slugs are by definition supposed to be ascii only...infact I just checked the source for django's slugify and it's stipping unicode - John 2012-04-03 23:01


0

This is another way which will help in other functions you want to do the same. I'll asume the model's name you are using is 'Item'.

def slug_resilient_decorator(class_type):

    def slug_resilient_wrapper(obj):

            if obj.has_attr('pk'):
                    return obj
            else:
                    return class_type.objects.get(slug=obj)

    return wrapper

@slug_resilient_decorator(Item)
def slug_resilient_detail_view(obj):

    ...
2012-04-04 01:45
by fceruti