Package pybaz :: Module _location
[frames] | no frames]

Source Code for Module pybaz._location

  1  # arch-tag: fd18ae24-e724-44c2-92fe-627495793f4d 
  2  # Copyright (C) 2005 Canonical Ltd. 
  3  #               Author: David Allouche <david@canonical.com> 
  4  # 
  5  #    This program is free software; you can redistribute it and/or modify 
  6  #    it under the terms of the GNU General Public License as published by 
  7  #    the Free Software Foundation; either version 2 of the License, or 
  8  #    (at your option) any later version. 
  9  # 
 10  #    This program is distributed in the hope that it will be useful, 
 11  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  #    GNU General Public License for more details. 
 14  # 
 15  #    You should have received a copy of the GNU General Public License 
 16  #    along with this program; if not, write to the Free Software 
 17  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 18   
 19  """Support archive locations.""" 
 20   
 21  import urllib 
 22   
 23  from pybaz import errors 
 24   
 25  __all__ = [ 
 26      'ArchiveLocation', 
 27      'ArchiveLocationParams', 
 28      ] 
 29   
 30   
31 -def _factory():
32 import pybaz 33 return pybaz.factory
34
35 -def _backend():
36 import pybaz 37 return pybaz.backend
38
39 -def is_valid_url(url):
40 """Is `url` a valid url for an archive location? 41 42 :type url: str 43 :rtype: bool 44 """ 45 # FIXME: this assumes unix file names -- David Allouche 2005-07-08 46 if url.startswith('/'): 47 return True # absolute path name 48 if '://' not in url: 49 return False # not a url 50 scheme = url.split('://')[0] 51 if scheme not in ('ftp', 'http', 'sftp', 'file'): 52 return False # malformed url or unspported scheme 53 if scheme == 'file' and not url.startswith('file:///'): 54 return False # only support same-host file urls 55 return True
56 57
58 -class ArchiveLocation(object):
59 """A location identified by an url and containing a Bazaar archive.""" 60
61 - def __init__(self, url):
62 error_message = ("ArchiveLocation parameter should be a str containing" 63 " an http, ftp, sftp url or an absolute file path," 64 " but was: %r") 65 if not isinstance(url, str): 66 raise TypeError(error_message % (url,)) 67 if not is_valid_url(url): 68 raise ValueError(error_message % (url,)) 69 self._url = str(url) 70 self._archive = None
71
72 - def _get_url(self):
73 return self._url
74 75 url = property(_get_url, doc=""" 76 Url of this location. 77 78 :type: str 79 """) 80
81 - def __eq__(self, other):
82 """Compare equal to instances of ArchiveLocation with the same url.""" 83 if not _factory().isArchiveLocation(other): 84 return False 85 return self.url == other.url
86
87 - def __ne__(self, other):
88 """Logical complement of __eq__.""" 89 return not self == other
90
91 - def __repr__(self):
92 # NOTE: relies on the __module__ of this class to be overriden 93 return '%s.%s(%r)' % ( 94 self.__class__.__module__, self.__class__.__name__, self.url)
95
96 - def create_master(self, archive, params):
97 """Create a new master archive at this location. 98 99 :precondition: not self.is_registered() 100 and not archive.is_registered() 101 and <url does not exist and is writable> 102 :postcondition: archive.is_registered() and archive.location == self 103 and <url exists> 104 105 :type archive: Archive 106 :type params: ArchiveLocationParams 107 """ 108 self._check_make_archive_params(archive, params) 109 args = ['make-archive'] 110 if params.signed: 111 args.append('--signed') 112 if params.listing: 113 args.append('--listing') 114 if params._tla: 115 args.append('--tla') 116 args.extend((archive.name, self.url)) 117 _backend().null_cmd(args)
118
119 - def create_mirror(self, archive, params):
120 """Create a new archive mirror at this location. 121 122 :precondition: not self.is_registered() 123 and <url does not exist and is writable> 124 :postcondition: self.is_registered() 125 and <url exists> 126 127 :type archive: Archive 128 :type params: ArchiveLocationParams 129 """ 130 self._check_make_archive_params(archive, params) 131 args = ['make-archive', '--mirror', archive.name] 132 if params.signed: 133 args.append('--signed') 134 if params.listing: 135 args.append('--listing') 136 if params._tla: 137 args.append('--tla') 138 args.append(self.url) 139 _backend().null_cmd(args)
140
141 - def _check_make_archive_params(self, archive, params):
142 """Check sanity of create_master and create_mirror params.""" 143 if not _factory().isArchive(archive): 144 raise TypeError("ArchiveLocation.create_master archive argument" 145 " must be an archive, but was: %r" % archive) 146 if not isinstance(params, ArchiveLocationParams): 147 raise TypeError( 148 "ArchiveLocation.create_master params argument" 149 " must be an ArchiveLocationParams, but was %r" % archive)
150 # TODO: preconditions are not checked yet that would require spawning a 151 # couple of baz commands. Breaking preconditions will currently 152 # translate into an ExecProblem -- David Allouche 2005-07-09 153
154 - def is_registered(self):
155 """Is this location registered? 156 157 :rtype: bool 158 """ 159 for line in _backend().sequence_cmd(['archives', '--all-locations']): 160 if not line.startswith(' '): 161 continue 162 url = urllib.unquote(line[4:]) 163 if self.url == url: 164 return True 165 return False
166
167 - def unregister(self):
168 """Unregister this location: 169 170 :precondition: self.is_registered() 171 :poscondition: not self.is_registered() 172 :raises errors.LocationNotRegistered: this location was not registered. 173 """ 174 if not self.is_registered(): 175 # costly precondition check, but unregister is not a frequently 176 # used command. 177 raise errors.LocationNotRegistered(self.url) 178 _backend().null_cmd(('register-archive', '--delete', self.url))
179
180 - def register(self):
181 """Register this location. 182 183 :precondition: not self.is_registered() 184 :postcondition: self.is_registered() 185 :raises errors.LocationAlreadyRegistered: this location was already 186 registered. 187 """ 188 if self.is_registered(): 189 # costly precondition check, but register is not a frequently used 190 # command. 191 raise errors.LocationAlreadyRegistered(self.url) 192 _backend().null_cmd(['register-archive', self.url])
193
194 - def meta_info(self, key):
195 """Read a meta-info from this location. 196 197 :precondition: self.is_registered() 198 :param key: name of the meta-info to read. 199 :type key: str 200 :raises errors.MetaInfoError: this location has no such meta-info. 201 :raises errors.LocationNotRegistered: this location is not registered. 202 203 :bug: will raise `errors.MetaInfoError` when the location could not be 204 accessed, because baz gives us exit status 1 for ''meta-info not 205 present'' and ''could not access location''. 206 """ 207 error_message = ("ArchiveLocation.meta_info parameter should be a" 208 " non-empty str, but was: %r" % key) 209 if not isinstance(key, str): 210 raise TypeError(error_message) 211 if key == '': 212 raise ValueError(error_message) 213 if not self.is_registered(): 214 # expensive precondition check 215 raise errors.LocationNotRegistered(self.url) 216 # XXX: baz does not allow us to distinguish between "location could not 217 # be accessed" and "meta-info was not present" from the exit status. So 218 # we will raise MetaInfoError inappropriately when the archive could 219 # not be accessed. -- David Allouche 2005-07-12 220 args = ['archive-meta-info', self.url + '/' + key] 221 status, output = _backend().status_one_cmd(args, expected=(0, 1)) 222 if status != 0: 223 raise errors.MetaInfoError(self.url, key) 224 return output
225
226 - def _meta_info_present(self, key):
227 """Is the specified meta info present at this location? 228 229 :precondition: self.is_registered() 230 :param key: name of the meta-info to look for 231 :type key: str 232 :raises errors.LocationNotRegistered: this location is not registered. 233 234 :bug: because of the `meta_info` bug, that is completely unreliable, 235 that's why it's a protected method. Still it's useful for testing. 236 """ 237 try: 238 self.meta_info(key) 239 except errors.MetaInfoError: 240 return False 241 else: 242 return True
243
244 - def archive(self):
245 """Archive that is associated to this location. 246 247 That's a convenience method based on meta_info() that memoises its 248 result. 249 250 :rtype: `Archive` 251 """ 252 if self._archive is None: 253 name = self.meta_info('name') 254 self._archive = _factory().Archive(name) 255 return self._archive
256
257 - def _version_string(self):
258 """Version string of the archive. 259 260 Contents of the ``.archive-version`` file at the root of the archive. 261 262 :rtype: str 263 :bug: only works with schemes supported by ``urllib.urlopen``. 264 """ 265 url = self.url 266 version_url = self.url + '/.archive-version' 267 version_file = urllib.urlopen(version_url) 268 try: 269 version = version_file.read() 270 finally: 271 version_file.close() 272 return version.rstrip()
273
274 - def make_mirrorer(self, target):
275 """Create a mirrorer to mirror from this location to the target. 276 277 :param target: specific location the `MirrorMethod` will mirror to. 278 :type target: `ArchiveLocation` 279 :rtype: `MirrorMethod` 280 281 :raises error.LocationNotRegistered: at least one of self and target is 282 not a registered location. 283 :raises errors.MirrorLocationMismatch: self and target are registered 284 locations for different archives. 285 """ 286 if not _factory().isArchiveLocation(target): 287 raise TypeError("ArchiveLocation.make_mirrorer argument should be" 288 " and ArchiveLocation, but was: %r" % target) 289 if self.url == target.url: 290 raise ValueError("ArchiveLocation.make_mirrorer argument was an" 291 " ArchiveLocation with the same url as self.") 292 if not self.is_registered(): 293 raise errors.LocationNotRegistered(self) 294 if not target.is_registered(): 295 raise errors.LocationNotRegistered(target) 296 if self.archive() != target.archive(): 297 raise errors.MirrorLocationMismatch(self, target) 298 mirrorer = MirrorMethod(self, target) 299 return mirrorer
300 301
302 -class ArchiveLocationParams(object):
303 """Parameter Object used for creating archives masters and mirrors. 304 305 :ivar signed: create signed location? 306 :type signed: bool 307 :ivar listing: create location with listings for http access. 308 :type listing: bool 309 """ 310
311 - def __init__(self):
312 self.signed = False 313 self.listing = False 314 self._tla = False
315
316 - def tla_format(self):
317 """Set the parameter object to create a tla-compatible archive.""" 318 self._tla = True
319 320
321 -class MirrorMethod(object):
322 """Method Object used for mirroring operations. 323 324 This class should never be instantiated directly. Instead, it is created by 325 factory methods like `ArchiveLocation.make_mirrorer`. 326 """ 327
328 - def __init__(self, source, target):
329 """Do not instanciate this class directly.""" 330 self._source = source 331 self._target = target
332
333 - def _limit_type_is_bad(self, limit):
334 """Internal helper for sanity checking of limit argument.""" 335 if _factory().isVersion(limit): 336 return False 337 if _factory().isRevision(limit): 338 return False 339 return True
340
341 - def _limit_value_is_bad(self, limit):
342 """Internal helper for sanity checking of limit argument.""" 343 return limit.archive != self._source.archive()
344
345 - def mirror(self, limit=None):
346 """Perform mirroring. 347 348 :param limit: if provided, limit the mirroring to the specified 349 version or revision. 350 :type limit: `pybaz.Version`, `pybaz.Revision`, None 351 """ 352 assert self._source is not None 353 assert self._target is not None 354 args = ['archive-mirror', self._source.url, self._target.url] 355 if limit is not None: 356 message = ("MirrorMethod.mirror parameter should be a Version or" 357 " Revision in %s, but was: %r") 358 archive = self._source.archive() 359 if self._limit_type_is_bad(limit): 360 raise TypeError(message % (archive.name, limit)) 361 if self._limit_value_is_bad(limit): 362 raise ValueError(message % (archive.name, limit)) 363 args.append(limit.nonarch) 364 _backend().null_cmd(args)
365