[Grok-dev] Re: sprint mini-report 2

Brandon Craig Rhodes brandon at rhodesmill.org
Fri May 2 08:55:53 EDT 2008


Martijn Faassen <faassen at startifact.com> writes:

> I disagree. megrok.trails is a major philosophical change, while
> this codifies a pattern we've seen in current Grok applications.

True; but note that the megrok.trails TrailHead object can be attached
anywhere, to any object, in an app, and that once a Trail has been
followed, old-fashioned traversal takes over again.  So a Trail can be
deployed just to direct a small piece of a URL, and then to let normal
traversal take over again.  You don't, in other words, have to "do
your whole app" in Trails; the rest of your app can do traversal
normally.

Your other arguments are good ones, especially about traversable
attributes being not-really-about ORMs.  I suppose I'm just wary about
adding yet another way of traversing, and worried that we're going to
wind up with seven or ten different ways for traversal to happen by
the time Grok is really finished.  Right now, when Grok goes across
objects it detects at least:

 - Container lookup (object['foo']).
 - A traverse() method on an object.
 - A Traverser() class whose context is the current object.
 - A view, if we are at the last element of the URL.

As of this morning (and maybe I just need to re-read your new
handbook/whatever-it's-called!), I couldn't even tell you in which
order the above methods are detected, or in which cases they combine
safely (I *think* you can have both a View and container lookup
working at the same time?) and in which cases they trump each other (I
*think* that having a traverse() method turns off container lookup?).

And if we now add a way to signal automatic attribute traversal, then
what happens to our semantics?  Can a container also offer some of its
attributes for lookup, or not?  If an attribute has the same name as a
contained object, who wins?  Can you say that three attributes are
traversable, but then define a traverse() for a few more names that
wouldn't work as attributes?  And if so, in what order are they
performed?

So, first, I worry about the semantics of traversal becoming - or
maybe already having become! - too difficult to fit in my head.  One
of the things I love about Python is how easily I can keep its
language feature set in my head, and I was hoping Grok could keep the
number of rules down as well.

Additionally, I think that one reason I cringed at the idea of the ORM
story assuming that people will build up hierarchies of containers and
traversable objects (which, as you explained, is yet to be decided) is
that, the one time I tried it on a real application back in August, it
created *horrible* spaghetti code.  When I wanted to debug why the URL
for, say, an "Account" wasn't working, I had to get a pencil and paper
and trace through three or four levels of classes, following their
context() declarations, and looking around throught the source code
for Traverser() objects and so forth.  It was not at all clear how far
one has to read through the code (or whether there was a limit at
all!) to figure out how URLs were constructed.

> class PersonContainer(grok.Container): pass
> class Person(grok.Model): pass
> class AccountContainer(grok.Container): pass
> class Account(grok.Container): pass
>
> class App(grok.Model):
>     grok.traverse('person')
>     grok.traverse('account')
>
>     def __init__(self):
>          self.person = PersonContainer()
>          self.account = AccountContainer()

What if, in addition to "person" and "account", they have two or three
traversal targets that need to be implemented in code?  Can they
combine the above with a traverse() method?  Or will the rule be that
they can either grok.traverse(), or have a traverse() method, but not
both?

Often a traverse() method takes the form of an if-else; if those
containers needed to be created dynamically (for example, if they
needed to be bound to a different database depending on the preceding
part of the URL), then we would have the usual:

class ...

    def traverse(self, name):
        if name == 'person':
            return PersonContainer(self.db)
        elif name == 'account':
            return AccountContainer(self.db)

But this, it surely strikes any reader, is exactly the sort of thing
that Python invented properties to solve.  So, following your example
above, we arrive at:

class ...
     grok.traverse('person')
     grok.traverse('account')

    @property
    def person(self):
        return PersonContainer(self.db)

    @property
    def account(self):
        return AccountContainer(self.db)

But surely we have to admit that this is a bit awkward - having to
repeat the name of the property just in order to declare it
traversable?  Surely what we really want is a decorator:

class ...
    @grok.traversable
    @property
    def person(self):
        return PersonContainer(self.db)

    @grok.traversable
    @property
    def account(self):
        return AccountContainer(self.db)

There!  Now we don't have to repeat the property name again, with all
the problems that causes for readability and maintainability.  It
would also allow any normal security decorators to be added to
restrict certain URLs.

So if this really cool idea gained support, we would have *six* ways
of resolving what a "/foo" at the end of a URL could mean.

Again, I like simple.  I am very tempted at the moment (but I have
never set out all of the traversal ideas together, so I'm still
thinking through this) to suggest that all of this just doesn't belong
in a class, but in a separate traverser.

class AppTraverser(grok.Traverser):
    grok.context(App)
    traverse_attributes = ['person', 'account']

Hmm.  Never mind.  However I try to imagine this, it all becomes
spaghetti with rules that I'll never remember.  I know that traversal
will not be simple, but I would love if we could make it no more
complex as, say, Python attributes access, which I think has four
rules (__get_attribute__, __getattr__, properties, fall-through to
__dict__?).

> We noticed a pattern common in Grok applications: use of the
> 'traverse()' method to do this, or misuse of containers to contain
> heterogeneous objects. This we want to get rid of.

That's a good argument.  Again, you seem to have identified the same
kind of annoying if-else-ness cropping up in traverse() that tends to
crop up in __getattr__ statements, and attempted to let the user
decompose it into little assertions that the framework/language puts
back together into an if-then for them - with all the wonderful things
that does when you want to inherit from a class and just add one
little attribute without breaking all of the other things it does.

> What happens if you go to /person or /account directly? Are there
> models and views for that?

No, since there's no such thing as a "person" or "account" without an
identity.  I have separate "/people" and "/accounts" objects for
searching and browsing.

> I'd argue that if you have a URL called /a/b you typically want /a
> to at least not return a 404 error.

Hrm.  You've got me there.  Flickr actually does pluralize the
components of URLs; note "photos" and "sets":

http://www.flickr.com/photos/brandonrhodes/sets/89271/

Which means that if you chop off the ID, you get a view of all of
those sorts of things.  Just like you're suggesting.

I guess that's a big unresolved issue of URL, and even file, naming.
Do you do like the Unix people, and name the directory "bin" so that
the file name "/usr/bin/foo" reads reasonably, or do you name the
directory in the plural - "bins" or whatever - so that "/usr/bins"
correctly names the container as containing several things?  It's like
the big battle amongst database folks as to whether tables get
singular or plural names. :-) (Last year I switched to singular names
after years of doing plural.)

-- 
Brandon Craig Rhodes   brandon at rhodesmill.org   http://rhodesmill.org/brandon


More information about the Grok-dev mailing list