Package trac :: Module resource

Source Code for Module trac.resource

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2006-2020 Edgewall Software 
  4  # Copyright (C) 2006-2007 Alec Thomas <[email protected]> 
  5  # Copyright (C) 2007 Christian Boos <[email protected]> 
  6  # All rights reserved. 
  7  # 
  8  # This software is licensed as described in the file COPYING, which 
  9  # you should have received as part of this distribution. The terms 
 10  # are also available at https://trac.edgewall.org/wiki/TracLicense. 
 11  # 
 12  # This software consists of voluntary contributions made by many 
 13  # individuals. For the exact contribution history, see the revision 
 14  # history and logs, available at https://trac.edgewall.org/log/. 
 15  # 
 16  # Author: Christian Boos <[email protected]> 
 17  #         Alec Thomas <[email protected]> 
 18   
 19  from trac.core import * 
 20  from trac.util.translation import _ 
 21   
 22   
23 -class ResourceNotFound(TracError):
24 """Thrown when a non-existent resource is requested"""
25 26
27 -class IResourceManager(Interface):
28
30 """Return resource realms managed by the component. 31 32 :rtype: `basestring` generator 33 """
34
35 - def get_resource_url(resource, href, **kwargs):
36 """Return the canonical URL for displaying the given resource. 37 38 :param resource: a `Resource` 39 :param href: an `Href` used for creating the URL 40 41 Note that if there's no special rule associated to this realm for 42 creating URLs (i.e. the standard convention of using realm/id applies), 43 then it's OK to not define this method. 44 """
45
46 - def get_resource_description(resource, format='default', context=None, 47 **kwargs):
48 """Return a string representation of the resource, according to the 49 `format`. 50 51 :param resource: the `Resource` to describe 52 :param format: the kind of description wanted. Typical formats are: 53 `'default'`, `'compact'` or `'summary'`. 54 :param context: an optional rendering context to allow rendering rich 55 output (like markup containing links) 56 :type context: `ResourceContext` 57 58 Additional keyword arguments can be given as extra information for 59 some formats. 60 61 For example, the ticket with the id 123 is represented as: 62 - `'#123'` in `'compact'` format, 63 - `'Ticket #123'` for the `default` format. 64 - `'Ticket #123 (closed defect): This is the summary'` for the 65 `'summary'` format 66 67 Note that it is also OK to not define this method if there's no 68 special way to represent the resource, in which case the standard 69 representations 'realm:id' (in compact mode) or 'Realm id' (in 70 default mode) will be used. 71 """
72
73 - def resource_exists(resource):
74 """Check whether the given `resource` exists physically. 75 76 :rtype: bool 77 78 Attempting to retrieve the model object for a non-existing 79 resource should raise a `ResourceNotFound` exception. 80 (''since 0.11.8'') 81 """
82 83
84 -class Resource(object):
85 """Resource identifier. 86 87 This specifies as precisely as possible *which* resource from a Trac 88 environment is manipulated. 89 90 A resource is identified by: 91 - a `realm` (a string like `'wiki'` or `'ticket'`) 92 - an `id`, which uniquely identifies a resource within its realm. 93 If the `id` information is not set, then the resource represents 94 the realm as a whole. 95 - an optional `version` information. 96 If `version` is `None`, this refers by convention to the latest 97 version of the resource. 98 99 Some generic and commonly used rendering methods are associated as well 100 to the Resource object. Those properties and methods actually delegate 101 the real work to the Resource's manager. 102 """ 103 104 __slots__ = ('realm', 'id', 'version', 'parent') 105
106 - def __repr__(self):
107 path = [] 108 r = self 109 while r: 110 name = r.realm 111 if r.id: 112 name += ':' + unicode(r.id) # id can be numerical 113 if r.version is not None: 114 name += '@' + unicode(r.version) 115 path.append(name or '') 116 r = r.parent 117 return '<Resource %r>' % (', '.join(reversed(path)))
118
119 - def __eq__(self, other):
120 return self.realm == other.realm and \ 121 self.id == other.id and \ 122 self.version == other.version and \ 123 self.parent == other.parent
124
125 - def __hash__(self):
126 """Hash this resource descriptor, including its hierarchy.""" 127 path = () 128 current = self 129 while current: 130 path += (self.realm, self.id, self.version) 131 current = current.parent 132 return hash(path)
133 134 # -- methods for creating other Resource identifiers 135
136 - def __new__(cls, resource_or_realm=None, id=False, version=False, 137 parent=False):
138 """Create a new Resource object from a specification. 139 140 :param resource_or_realm: this can be either: 141 - a `Resource`, which is then used as a base for making a copy 142 - a `basestring`, used to specify a `realm` 143 :param id: the resource identifier 144 :param version: the version or `None` for indicating the latest version 145 146 >>> main = Resource('wiki', 'WikiStart') 147 >>> repr(main) 148 "<Resource u'wiki:WikiStart'>" 149 150 >>> Resource(main) is main 151 True 152 153 >>> main3 = Resource(main, version=3) 154 >>> repr(main3) 155 "<Resource u'wiki:WikiStart@3'>" 156 157 >>> main0 = main3(version=0) 158 >>> repr(main0) 159 "<Resource u'wiki:WikiStart@0'>" 160 161 In a copy, if `id` is overriden, then the original `version` value 162 will not be reused. 163 164 >>> repr(Resource(main3, id="WikiEnd")) 165 "<Resource u'wiki:WikiEnd'>" 166 167 >>> repr(Resource(None)) 168 "<Resource ''>" 169 """ 170 realm = resource_or_realm 171 if isinstance(resource_or_realm, Resource): 172 if id is False and version is False and parent is False: 173 return resource_or_realm 174 else: # copy and override 175 realm = resource_or_realm.realm 176 if id is False: 177 id = resource_or_realm.id 178 if version is False: 179 if id == resource_or_realm.id: 180 version = resource_or_realm.version # could be 0... 181 else: 182 version = None 183 if parent is False: 184 parent = resource_or_realm.parent 185 else: 186 if id is False: 187 id = None 188 if version is False: 189 version = None 190 if parent is False: 191 parent = None 192 resource = super(Resource, cls).__new__(cls) 193 resource.realm = realm 194 resource.id = id 195 resource.version = version 196 resource.parent = parent 197 return resource
198
199 - def __call__(self, realm=False, id=False, version=False, parent=False):
200 """Create a new Resource using the current resource as a template. 201 202 Optional keyword arguments can be given to override `id` and 203 `version`. 204 """ 205 return Resource(self if realm is False else realm, id, version, parent)
206 207 # -- methods for retrieving children Resource identifiers 208
209 - def child(self, realm, id=False, version=False):
210 """Retrieve a child resource for a secondary `realm`. 211 212 Same as `__call__`, except that this one sets the parent to `self`. 213 214 >>> repr(Resource(None).child('attachment', 'file.txt')) 215 "<Resource u', attachment:file.txt'>" 216 """ 217 return Resource(realm, id, version, self)
218 219
220 -class ResourceSystem(Component):
221 """Resource identification and description manager. 222 223 This component makes the link between `Resource` identifiers and their 224 corresponding manager `Component`. 225 """ 226 227 resource_managers = ExtensionPoint(IResourceManager) 228
229 - def __init__(self):
230 self._resource_managers_map = None
231 232 # Public methods 233
234 - def get_resource_manager(self, realm):
235 """Return the component responsible for resources in the given `realm` 236 237 :param realm: the realm name 238 :return: a `Component` implementing `IResourceManager` or `None` 239 """ 240 # build a dict of realm keys to IResourceManager implementations 241 if not self._resource_managers_map: 242 map = {} 243 for manager in self.resource_managers: 244 for manager_realm in manager.get_resource_realms() or []: 245 map[manager_realm] = manager 246 self._resource_managers_map = map 247 return self._resource_managers_map.get(realm)
248
249 - def get_known_realms(self):
250 """Return a list of all the realm names of resource managers.""" 251 realms = [] 252 for manager in self.resource_managers: 253 for realm in manager.get_resource_realms() or []: 254 realms.append(realm) 255 return realms
256 257 258 # -- Utilities for manipulating resources in a generic way 259
260 -def get_resource_url(env, resource, href, **kwargs):
261 """Retrieve the canonical URL for the given resource. 262 263 This function delegates the work to the resource manager for that 264 resource if it implements a `get_resource_url` method, otherwise 265 reverts to simple '/realm/identifier' style URLs. 266 267 :param env: the `Environment` where `IResourceManager` components live 268 :param resource: the `Resource` object specifying the Trac resource 269 :param href: an `Href` object used for building the URL 270 271 Additional keyword arguments are translated as query paramaters in the URL. 272 273 >>> from trac.test import EnvironmentStub 274 >>> from trac.web.href import Href 275 >>> env = EnvironmentStub() 276 >>> href = Href('/trac.cgi') 277 >>> main = Resource('generic', 'Main') 278 >>> get_resource_url(env, main, href) 279 '/trac.cgi/generic/Main' 280 281 >>> get_resource_url(env, main(version=3), href) 282 '/trac.cgi/generic/Main?version=3' 283 284 >>> get_resource_url(env, main(version=3), href) 285 '/trac.cgi/generic/Main?version=3' 286 287 >>> get_resource_url(env, main(version=3), href, action='diff') 288 '/trac.cgi/generic/Main?action=diff&version=3' 289 290 >>> get_resource_url(env, main(version=3), href, action='diff', version=5) 291 '/trac.cgi/generic/Main?action=diff&version=5' 292 293 """ 294 manager = ResourceSystem(env).get_resource_manager(resource.realm) 295 if manager and hasattr(manager, 'get_resource_url'): 296 return manager.get_resource_url(resource, href, **kwargs) 297 args = {'version': resource.version} 298 args.update(kwargs) 299 return href(resource.realm, resource.id, **args)
300
301 -def get_resource_description(env, resource, format='default', **kwargs):
302 """Retrieve a standardized description for the given resource. 303 304 This function delegates the work to the resource manager for that 305 resource if it implements a `get_resource_description` method, 306 otherwise reverts to simple presentation of the realm and identifier 307 information. 308 309 :param env: the `Environment` where `IResourceManager` components live 310 :param resource: the `Resource` object specifying the Trac resource 311 :param format: which formats to use for the description 312 313 Additional keyword arguments can be provided and will be propagated 314 to resource manager that might make use of them (typically, a `context` 315 parameter for creating context dependent output). 316 317 >>> from trac.test import EnvironmentStub 318 >>> env = EnvironmentStub() 319 >>> main = Resource('generic', 'Main') 320 >>> get_resource_description(env, main) 321 u'generic:Main' 322 323 >>> get_resource_description(env, main(version=3)) 324 u'generic:Main' 325 326 >>> get_resource_description(env, main(version=3), format='summary') 327 u'generic:Main at version 3' 328 329 """ 330 manager = ResourceSystem(env).get_resource_manager(resource.realm) 331 if manager and hasattr(manager, 'get_resource_description'): 332 return manager.get_resource_description(resource, format, **kwargs) 333 name = u'%s:%s' % (resource.realm, resource.id) 334 if format == 'summary': 335 name = _('%(name)s at version %(version)s', 336 name=name, version=resource.version) 337 return name
338
339 -def get_resource_name(env, resource):
340 return get_resource_description(env, resource)
341
342 -def get_resource_shortname(env, resource):
343 return get_resource_description(env, resource, 'compact')
344
345 -def get_resource_summary(env, resource):
346 return get_resource_description(env, resource, 'summary')
347
348 -def get_relative_resource(resource, path=''):
349 """Build a Resource relative to a reference resource. 350 351 :param path: path leading to another resource within the same realm. 352 """ 353 if path in (None, '', '.'): 354 return resource 355 else: 356 base = unicode(resource.id if path[0] != '/' else '').split('/') 357 for comp in path.split('/'): 358 if comp == '..': 359 if base: 360 base.pop() 361 elif comp and comp != '.': 362 base.append(comp) 363 return resource(id='/'.join(base) if base else None)
364
365 -def get_relative_url(env, resource, href, path='', **kwargs):
366 """Build an URL relative to a resource given as reference. 367 368 :param path: path leading to another resource within the same realm. 369 370 >>> from trac.test import EnvironmentStub 371 >>> env = EnvironmentStub() 372 >>> from trac.web.href import Href 373 >>> href = Href('/trac.cgi') 374 >>> main = Resource('wiki', 'Main', version=3) 375 376 Without parameters, return the canonical URL for the resource, like 377 `get_resource_url` does. 378 379 >>> get_relative_url(env, main, href) 380 '/trac.cgi/wiki/Main?version=3' 381 382 Paths are relative to the given resource: 383 384 >>> get_relative_url(env, main, href, '.') 385 '/trac.cgi/wiki/Main?version=3' 386 387 >>> get_relative_url(env, main, href, './Sub') 388 '/trac.cgi/wiki/Main/Sub' 389 390 >>> get_relative_url(env, main, href, './Sub/Infra') 391 '/trac.cgi/wiki/Main/Sub/Infra' 392 393 >>> get_relative_url(env, main, href, './Sub/') 394 '/trac.cgi/wiki/Main/Sub' 395 396 >>> mainsub = main(id='Main/Sub') 397 >>> get_relative_url(env, mainsub, href, '..') 398 '/trac.cgi/wiki/Main' 399 400 >>> get_relative_url(env, main, href, '../Other') 401 '/trac.cgi/wiki/Other' 402 403 References always stay within the current resource realm: 404 405 >>> get_relative_url(env, mainsub, href, '../..') 406 '/trac.cgi/wiki' 407 408 >>> get_relative_url(env, mainsub, href, '../../..') 409 '/trac.cgi/wiki' 410 411 >>> get_relative_url(env, mainsub, href, '/toplevel') 412 '/trac.cgi/wiki/toplevel' 413 414 Extra keyword arguments are forwarded as query parameters: 415 416 >>> get_relative_url(env, main, href, action='diff') 417 '/trac.cgi/wiki/Main?action=diff&version=3' 418 419 """ 420 return get_resource_url(env, get_relative_resource(resource, path), 421 href, **kwargs)
422 435
436 -def resource_exists(env, resource):
437 """Checks for resource existence without actually instantiating a model. 438 439 :return: `True` if the resource exists, `False` if it doesn't 440 and `None` in case no conclusion could be made 441 (i.e. when `IResourceManager.resource_exists` is not 442 implemented). 443 444 >>> from trac.test import EnvironmentStub 445 >>> env = EnvironmentStub() 446 447 >>> resource_exists(env, Resource('dummy-realm', 'dummy-id')) is None 448 True 449 >>> resource_exists(env, Resource('dummy-realm')) 450 False 451 452 """ 453 manager = ResourceSystem(env).get_resource_manager(resource.realm) 454 if manager and hasattr(manager, 'resource_exists'): 455 return manager.resource_exists(resource) 456 elif resource.id is None: 457 return False
458