Alec Thomas wrote:
> On Fri, Jan 12, 2007 at 09:56:57AM +1100, Alec Thomas wrote:
>>>>         page = WikiPage(env, 'WikiStart')
>>>>         context = Context.from_resource(env, req, page)
>>> ... or simply `context = page.get_context(req)`, instead of the more 
>>> complex hops through the IResourceMapper implementations which will end 
>>> in the `identify_resource(req, resource)` of the module?
>> The only real reason I didn't do that was that it would ideally be in a
>> base "Trac Object" and, if we have that, lots of other stuff could/should
>> go in it, just like your old TracObject branch. I don't really have a
>> problem with adding it, but...
>
> I had a look at this, and I'd like to understand the actual objections
> that were raised regarding TracObjects. To me it makes sense to be able
> to obtain information about a Trac resource from the resource itself. In
> fact, with the current Context object you could almost rename "Context"
> to "Resource" and do this:
>
>
>     class WikiPage(Resource):
>         ... snip of a TracObject rewrite ;) 

Well, I'm not sure if I'm the best person to advocate the objections 
against the TracObject/Resource idea, but here's my take.

The main objection as I understood it was that it was bad design to make 
the data model layer aware of the web layer (the req).

A second issue is that it is somewhat costly to build a data model object.
In my early attempt, I distinguished between a loaded/unloaded 
TracObject, but it was somehow cumbersome to use right. I much prefer 
the (upcoming) distinction between (1) a Context, which acts as a kind 
of resource descriptor and also knows about the /context of use/ of that 
resource (e.g. the ''parent'' context) and (2) a Resource, which is the 
data model for that resource.

That way it's always clear what is what, and you can go easily from the 
"cheap" resource descriptor to the actual  resource object, and back.

Now another strong point of the TracObject proposal was the possibility 
to override some base methods. I think the Context is amenable to that 
as well.
Take the example of a "title" method that could be used to fill title 
attribute of a link to a given resource. That would be "<realm>:<id>" in 
the general case. But for tickets, we'd like to have something like 
"<ticket summary> (<status>[:<resolution if status=='closed'])". This of 
course would require to get access to the data model.

This could be implemented like this:
 
class TicketContext(Context):

    def title(self):
        status = self.resource['status']
        if status == 'closed':
            status += ':' + self.resource['resolution']
        return "%s: (%s)" % (self.resource['summary'], status)

In the above, self.resource is accessed many times transparently.
As a context is able to "cache" its data model object, it's not a 
problem, the access to the complete representation can be done 
efficiently and "on demand".

That also means that for a given realm, we need a mechanism to create 
the appropriate Resource subclass and the appropriate Context subclass. 
In my previous mail, I said that for getting a context from a resource, 
I wanted to use a Resource.get_context() method instead of going through 
the ResourceSystem. But after experimenting a bit, it seems as easy to 
use a helper function to achieve that, and this is less susceptible to 
be hit by the "mixing of layers" argument!

So this results in the following:

class IResourceMapper(Interface):
    """Map between Trac resources and their contexts."""

    def get_resource_classes():
        """Generator yielding `(realm, resource_subclass)` pairs."""

    def get_context_classes():
        """Generator yielding `(realm, context_subclass)` pairs."""

class ResourceSystem(Component):
    resource_mappers = ExtensionPoint(IResourceMapper)

    def create_context(self, req, realm, *args, **kwargs):
        if not self._context_map: 
            # build a dict of realm keys to Context subclasses values
        context_class = self._context_map.get(realm, Context)
        return context_class(self.env, req, realm, *args, **kwargs)
 
    def fetch_resource(self, context):
        if not self._resource_map:
            # build a dict of realm keys to Resource subclasses values
        resource_class = self._resource_map.get(realm)
        if resource_class:
            return resource_class(self.env, realm, context)
        # Note that passing a context object to the Resource constructor is 
arguable:
        # (+) constructor can fetch additional info if needed (e.g. version)
        # (-) Resource should never store references to the Context, to avoid
        #     circular refs. So ideally, Resources shouldn't know about Contexts


class Context(object):
    """Base class for resource descriptors."""
    ...
    # now, creating a sub-context (__call__) needs to be modified to use the
    # ResourceSystem as well, in order to be able to create instances of the
    # appropriate subclass


class Resource(object):
    """Base class for resources."""

    realm = 'unknown' # needs to be overridden

    # Note that an `.id` property needs to be set for the instances as well


And here's the helper function I was talking about:

def create_context(resource, req, *args, **kwargs):
    rs = ResourceSystem(resource.env)
    kwargs['resource'] = resource
    return rs.create_context(req, resource.realm, resource.id, *args, **kwargs)


Well, that's was too much coding in Thunderbird, let's do it for real ;)

So again, about the merge in trunk:
 - I'd like we sort out the above first
 - I'd like to merge my 3 other branches (blame and the 2 -tmp ones) 
before ;)

-- Christian

--~--~---------~--~----~------------~-------~--~----~
 You received this message because you are subscribed to the Google Groups 
"Trac Development" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/trac-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to