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
-~----------~----~----~----~------~----~------~--~---