1############################################################################## 2# 3# Copyright (c) 2003-2009 Zope Foundation and Contributors. 4# All Rights Reserved. 5# 6# This software is subject to the provisions of the Zope Public License, 7# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11# FOR A PARTICULAR PURPOSE. 12# 13############################################################################## 14"""Classes to support implenting IContained 15""" 16__docformat__ = 'restructuredtext' 17 18import zope.component 19import zope.component.interfaces 20import zope.interface 21from zope.location.interfaces import ILocationInfo 22from zope.location.interfaces import ILocation, IRoot 23from zope.location.location import Location 24 25 26class LocationPhysicallyLocatable(object): 27 """Provide location information for location objects 28 29 >>> from zope.interface.verify import verifyObject 30 >>> info = LocationPhysicallyLocatable(Location()) 31 >>> verifyObject(ILocationInfo, info) 32 True 33 34 """ 35 36 zope.component.adapts(ILocation) 37 zope.interface.implements(ILocationInfo) 38 39 def __init__(self, context): 40 self.context = context 41 42 def getRoot(self): 43 """Get the root location for a location. 44 45 See ILocationInfo 46 47 The root location is a location that contains the given 48 location and that implements IContainmentRoot. 49 50 >>> root = Location() 51 >>> zope.interface.directlyProvides(root, IRoot) 52 >>> LocationPhysicallyLocatable(root).getRoot() is root 53 True 54 55 >>> o1 = Location(); o1.__parent__ = root 56 >>> LocationPhysicallyLocatable(o1).getRoot() is root 57 True 58 59 >>> o2 = Location(); o2.__parent__ = o1 60 >>> LocationPhysicallyLocatable(o2).getRoot() is root 61 True 62 63 We'll get a TypeError if we try to get the location fo a 64 rootless object: 65 66 >>> o1.__parent__ = None 67 >>> LocationPhysicallyLocatable(o1).getRoot() 68 Traceback (most recent call last): 69 ... 70 TypeError: Not enough context to determine location root 71 >>> LocationPhysicallyLocatable(o2).getRoot() 72 Traceback (most recent call last): 73 ... 74 TypeError: Not enough context to determine location root 75 76 If we screw up and create a location cycle, it will be caught: 77 78 >>> o1.__parent__ = o2 79 >>> LocationPhysicallyLocatable(o1).getRoot() 80 Traceback (most recent call last): 81 ... 82 TypeError: Maximum location depth exceeded, """ \ 83 """probably due to a a location cycle. 84 """ 85 context = self.context 86 max = 9999 87 while context is not None: 88 if IRoot.providedBy(context): 89 return context 90 context = context.__parent__ 91 max -= 1 92 if max < 1: 93 raise TypeError("Maximum location depth exceeded, " 94 "probably due to a a location cycle.") 95 96 raise TypeError("Not enough context to determine location root") 97 98 def getPath(self): 99 """Get the path of a location. 100 101 See ILocationInfo 102 103 This is an "absolute path", rooted at a root object. 104 105 >>> root = Location() 106 >>> zope.interface.directlyProvides(root, IRoot) 107 >>> LocationPhysicallyLocatable(root).getPath() 108 u'/' 109 110 >>> o1 = Location(); o1.__parent__ = root; o1.__name__ = 'o1' 111 >>> LocationPhysicallyLocatable(o1).getPath() 112 u'/o1' 113 114 >>> o2 = Location(); o2.__parent__ = o1; o2.__name__ = u'o2' 115 >>> LocationPhysicallyLocatable(o2).getPath() 116 u'/o1/o2' 117 118 It is an error to get the path of a rootless location: 119 120 >>> o1.__parent__ = None 121 >>> LocationPhysicallyLocatable(o1).getPath() 122 Traceback (most recent call last): 123 ... 124 TypeError: Not enough context to determine location root 125 126 >>> LocationPhysicallyLocatable(o2).getPath() 127 Traceback (most recent call last): 128 ... 129 TypeError: Not enough context to determine location root 130 131 If we screw up and create a location cycle, it will be caught: 132 133 >>> o1.__parent__ = o2 134 >>> LocationPhysicallyLocatable(o1).getPath() 135 Traceback (most recent call last): 136 ... 137 TypeError: Maximum location depth exceeded, """ \ 138 """probably due to a a location cycle. 139 140 """ 141 142 path = [] 143 context = self.context 144 max = 9999 145 while context is not None: 146 if IRoot.providedBy(context): 147 if path: 148 path.append('') 149 path.reverse() 150 return u'/'.join(path) 151 else: 152 return u'/' 153 path.append(context.__name__) 154 context = context.__parent__ 155 max -= 1 156 if max < 1: 157 raise TypeError("Maximum location depth exceeded, " 158 "probably due to a a location cycle.") 159 160 raise TypeError("Not enough context to determine location root") 161 162 def getParent(self): 163 """Returns the container the object was traversed via. 164 165 Returns None if the object is a containment root. 166 Raises TypeError if the object doesn't have enough context to get the 167 parent. 168 169 >>> root = Location() 170 >>> zope.interface.directlyProvides(root, IRoot) 171 >>> o1 = Location() 172 >>> o2 = Location() 173 174 >>> LocationPhysicallyLocatable(o2).getParent() # doctest: +ELLIPSIS 175 Traceback (most recent call last): 176 TypeError: ('Not enough context information to get parent', <zope.location.location.Location object at 0x...>) 177 178 >>> o1.__parent__ = root 179 >>> LocationPhysicallyLocatable(o1).getParent() == root 180 True 181 182 >>> o2.__parent__ = o1 183 >>> LocationPhysicallyLocatable(o2).getParent() == o1 184 True 185 186 """ 187 parent = getattr(self.context, '__parent__', None) 188 if parent is not None: 189 return parent 190 191 raise TypeError('Not enough context information to get parent', 192 self.context) 193 194 def getParents(self): 195 """Returns a list starting with the object's parent followed by 196 each of its parents. 197 198 Raises a TypeError if the object is not connected to a containment 199 root. 200 201 >>> root = Location() 202 >>> zope.interface.directlyProvides(root, IRoot) 203 >>> o1 = Location() 204 >>> o2 = Location() 205 >>> o1.__parent__ = root 206 >>> o2.__parent__ = o1 207 >>> LocationPhysicallyLocatable(o2).getParents() == [o1, root] 208 True 209 210 If the last parent is not an IRoot object, TypeError will be 211 raised as statet before. 212 213 >>> zope.interface.noLongerProvides(root, IRoot) 214 >>> LocationPhysicallyLocatable(o2).getParents() 215 Traceback (most recent call last): 216 ... 217 TypeError: Not enough context information to get all parents 218 219 """ 220 # XXX Merge this implementation with getPath. This was refactored 221 # from zope.traversing. 222 parents = [] 223 w = self.context 224 while 1: 225 w = w.__parent__ 226 if w is None: 227 break 228 parents.append(w) 229 230 if parents and IRoot.providedBy(parents[-1]): 231 return parents 232 233 raise TypeError("Not enough context information to get all parents") 234 235 def getName(self): 236 """Get a location name 237 238 See ILocationInfo 239 240 >>> o1 = Location(); o1.__name__ = u'o1' 241 >>> LocationPhysicallyLocatable(o1).getName() 242 u'o1' 243 """ 244 return self.context.__name__ 245 246 def getNearestSite(self): 247 """return the nearest site, see ILocationInfo 248 249 >>> o1 = Location() 250 >>> o1.__name__ = 'o1' 251 >>> LocationPhysicallyLocatable(o1).getNearestSite() 252 Traceback (most recent call last): 253 ... 254 TypeError: Not enough context information to get all parents 255 256 >>> root = Location() 257 >>> zope.interface.directlyProvides(root, IRoot) 258 >>> o1 = Location() 259 >>> o1.__name__ = 'o1' 260 >>> o1.__parent__ = root 261 >>> LocationPhysicallyLocatable(o1).getNearestSite() is root 262 True 263 264 >>> zope.interface.directlyProvides( 265 ... o1, zope.component.interfaces.ISite) 266 >>> LocationPhysicallyLocatable(o1).getNearestSite() is o1 267 True 268 269 >>> o2 = Location() 270 >>> o2.__parent__ = o1 271 >>> LocationPhysicallyLocatable(o2).getNearestSite() is o1 272 True 273 274 """ 275 if zope.component.interfaces.ISite.providedBy(self.context): 276 return self.context 277 for parent in self.getParents(): 278 if zope.component.interfaces.ISite.providedBy(parent): 279 return parent 280 return self.getRoot() 281 282class RootPhysicallyLocatable(object): 283 """Provide location information for the root object 284 285 >>> from zope.interface.verify import verifyObject 286 >>> info = RootPhysicallyLocatable(None) 287 >>> verifyObject(ILocationInfo, info) 288 True 289 290 This adapter is very simple, because there's no places to search 291 for parents and nearest sites, so we are only working with context 292 object, knowing that its the root object already. 293 294 """ 295 296 zope.component.adapts(IRoot) 297 zope.interface.implements(ILocationInfo) 298 299 def __init__(self, context): 300 self.context = context 301 302 def getRoot(self): 303 """See ILocationInfo 304 305 No need to search for root when our context is already root :) 306 307 >>> o1 = object() 308 >>> RootPhysicallyLocatable(o1).getRoot() is o1 309 True 310 311 """ 312 return self.context 313 314 def getPath(self): 315 """See ILocationInfo 316 317 Root object is at the top of the tree, so always return ``/``. 318 319 >>> o1 = object() 320 >>> RootPhysicallyLocatable(o1).getPath() 321 u'/' 322 323 """ 324 return u'/' 325 326 def getName(self): 327 """See ILocationInfo 328 329 Always return empty unicode string for the root object 330 331 >>> o1 = object() 332 >>> RootPhysicallyLocatable(o1).getName() 333 u'' 334 335 """ 336 return u'' 337 338 def getParent(self): 339 """Returns the container the object was traversed via. 340 341 Returns None if the object is a containment root. 342 Raises TypeError if the object doesn't have enough context to get the 343 parent. 344 345 >>> o1 = object() 346 >>> RootPhysicallyLocatable(o1).getParent() 347 348 """ 349 return None 350 351 def getParents(self): 352 """See ILocationInfo 353 354 There's no parents for the root object, return empty list. 355 356 >>> o1 = object() 357 >>> RootPhysicallyLocatable(o1).getParents() 358 [] 359 360 """ 361 return [] 362 363 def getNearestSite(self): 364 """See ILocationInfo 365 366 Return object itself as the nearest site, because there's no 367 other place to look for. It's also usual that the root is the 368 site as well. 369 370 371 >>> o1 = object() 372 >>> RootPhysicallyLocatable(o1).getNearestSite() is o1 373 True 374 375 """ 376 return self.context 377