def parse_package_version_spec(val): """parse_package_version_spec(val) -> (pkgname, VersionNumber) or (pkgname, VersionSpec) or (pkgname, None) Parse a package identifier together with its version spec (e.g., "org.boodler.sample:1.0") or exact version spec (e.g., "org.boodler.sample::1.0"). If neither is present, the second value of the return tuple will be None. Raises a ValueError if the name was in any way invalid. (Thus, this function can be used to ensure that a package name is valid.) """ vers = None pos = val.find(':') if (pos >= 0): spec = val[pos + 1:] val = val[:pos] if (spec.startswith(':')): vers = version.VersionNumber(spec[1:]) else: vers = version.VersionSpec(spec) parse_package_name(val) return (val, vers)
def subtest_dependency_cases(self): pkg = self.loader.load('depend.on.cases') tup = pkg.load_dependencies() badls = [ ('missing.nospec', None), ('missing.spec', version.VersionSpec('2.0')), ('missing.num', version.VersionNumber('3.0')), ] badls.sort() baddict = dict([ (key,[val]) for (key,val) in badls ]) self.assertEqual(tup, (self.packages_to_set([ ('depend.on.cases', '1.0'), ('depend.one', '2.0'), ]), baddict, 0)) bad = self.loader.find_all_dependencies()[2] ls = bad[('depend.on.cases', version.VersionNumber('1.0'))] ls.sort() self.assertEqual(ls, badls)
def subtest_load_group(self): grp = self.loader.load_group('version.specs') ls = grp.get_versions() ls.sort() ls = [ str(val) for val in ls ] self.assertEqual(ls, ['1.2', '1.5', '1.5.3', '2.5']) self.assertEqual(grp.get_num_versions(), 4) vers = grp.find_version_match() self.assertEqual(vers, '2.5') vers = grp.find_version_match(version.VersionSpec('1')) self.assertEqual(vers, '1.5.3')
def load(self, pkgname, versionspec=None): """load(pkgname, versionspec=None) -> PackageInfo Load a package, given its name and a version spec. If no second argument is given, the most recent available version of the package is loaded. If the argument is a VersionNumber, that version will be loaded. If it is a VersionSpec, the most recent version that matches the spec will be loaded. A string value will be converted to a VersionSpec (not a VersionNumber). This generates a PackageNotFoundError if no matching package is available. It generates PackageLoadError if the package was malformed in some way which prevented loading. """ if (type(versionspec) in [str, unicode]): versionspec = version.VersionSpec(versionspec) pgroup = self.load_group(pkgname) if (not pgroup.get_num_versions()): raise PackageNotFoundError(pkgname, 'no versions available') if (isinstance(versionspec, version.VersionNumber)): if (not pgroup.has_version(versionspec)): raise PackageNotFoundError(pkgname, 'version \'' + str(versionspec) + '\' not available') vers = versionspec elif ((versionspec is None) or isinstance(versionspec, version.VersionSpec)): vers = pgroup.find_version_match(versionspec) else: raise PackageLoadError(pkgname, 'load spec must be a string, VersionSpec, or VersionNumber') if (not vers): raise PackageNotFoundError(pkgname, 'no version matching \'' + str(versionspec) + '\'') pkg = self.load_specific(pkgname, vers) if (self.currently_importing): # Record what package imported this one, and with what spec self.currently_importing.imported_pkg_specs[pkgname] = versionspec return pkg
def test_parse_package_version_spec(self): valid_list = [ 'foo:1.0', 'foo:2-', 'foo:-3.1', 'foo:2-4', 'foo::1.2.3', 'foo.bar::1.5.6a', ] invalid_list = [ '0x', ':foo', 'foo:0.1', 'foo:1.2.3', 'foo::0.1', 'foo:::1.0', 'foo:1:2', 'foo: 1.0', 'foo :1.0', 'foo: :1.0', 'foo:: 1.0', ] (pkg, vers) = parse_package_version_spec('x.y.z') self.assertEqual(pkg, 'x.y.z') self.assertEqual(vers, None) (pkg, vers) = parse_package_version_spec('x.y.z:2.4') self.assertEqual(pkg, 'x.y.z') self.assert_(isinstance(vers, version.VersionSpec)) self.assertEqual(vers, version.VersionSpec('2.4')) (pkg, vers) = parse_package_version_spec('x.y.z::2.4.6') self.assertEqual(pkg, 'x.y.z') self.assert_(isinstance(vers, version.VersionNumber)) self.assertEqual(vers, version.VersionNumber('2.4.6')) for val in valid_list: parse_package_version_spec(val) for val in invalid_list: self.assertRaises(ValueError, parse_package_version_spec, val)
def record_import(self, pkg, name, spec=None): """record_import(pkg, name, spec=None) -> None Note a bimport() call by a package being imported. This should only be called by bimport(). """ if (type(spec) in [str, unicode]): spec = version.VersionSpec(spec) if (isinstance(spec, version.VersionNumber)): isexact = True elif ((spec is None) or isinstance(spec, version.VersionSpec)): # ok isexact = False else: return ls = self.import_recorder.get(pkg.key) if (not ls): ls = [] self.import_recorder[pkg.key] = ls ls.append((name, spec))
def load_item_by_name(self, name, package=None): """load_item_by_name(name, package=None) -> value Given a string that names a resource -- for example, 'com.eblong.example/reptile.Hiss' -- import the module and return the resource object. If the string begins with a slash ('/boodle.builtin.NullAgent') then the regular Python modules are searched. No importing is done in this case; it is really intended only for the contents of boodle.agent. If the string ends with a slash ('com.eblong.example/'), then the module itself is returned. If the package argument is supplied (a PackageInfo object), it becomes the package to look in for unqualified resource names ('reptile.Hiss'). If no package argument is supplied, then an unqualified resource name raises ValueError. """ pos = name.find('/') if (pos < 0): if (package is None): raise ValueError('argument must be of the form package/Resource') mod = package.get_content() elif (pos == 0): # consult Python's module map name = name[ 1 : ] headtail = name.split('.', 1) if (len(headtail) != 2): raise ValueError('argument must be of the form package/Resource') (modname, name) = headtail mod = sys.modules.get(modname) if (mod is None): raise ValueError('not found in Python modules: ' + modname) else: pkgname = name[ : pos ] name = name[ pos+1 : ] pkgspec = None pos = pkgname.find(':') if (pos >= 0): val = pkgname[ pos+1 : ] pkgname = pkgname[ : pos ] if (val.startswith(':')): val = val[ 1 : ] pkgspec = version.VersionNumber(val) else: pkgspec = version.VersionSpec(val) package = self.load(pkgname, pkgspec) mod = package.get_content() if (not name): # "module/" returns the module itself return mod namels = name.split('.') try: res = mod for el in namels: res = getattr(res, el) return res except AttributeError, ex: raise ValueError('unable to load ' + name + ' (' + str(ex) + ')')
def setUp(self): basedir = tempfile.mkdtemp(prefix='test_pload') self.basedir = basedir coldir = os.path.join(basedir, 'Collection') os.makedirs(coldir) self.coldir = coldir self.loader = pload.PackageLoader(coldir, importing_ok=True) build_package(self.loader, 'simple.test', '1.0') build_package(self.loader, 'import.module', mod_content={'key':'val'}, mod_main='main') build_package(self.loader, 'import.module', '2.0', mod_content={'key':'val2'}, mod_main='__init__') build_package(self.loader, 'version.specs', '1.2') build_package(self.loader, 'version.specs', '1.5') build_package(self.loader, 'version.specs', '1.5.3') build_package(self.loader, 'version.specs', '2.5') self.empty_dir_path = os.path.join(basedir, 'empty-dir') os.mkdir(self.empty_dir_path) self.external_one_path = os.path.join(basedir, 'external-one') build_package(self.loader, 'external.one', '1.0', external_path=self.external_one_path, metadata={'key':'ext1'}) build_package(self.loader, 'external.two', '2.5', mod_content={'key':'orig'}) self.external_two_path = os.path.join(basedir, 'external-two') build_package(self.loader, 'external.two', '2.5', external_path=self.external_two_path, mod_content={'key':'replacement'}) build_package(self.loader, 'external.three', '2.0', mod_content={'key':'orig'}) self.external_three_path = os.path.join(basedir, 'external-three') build_package(self.loader, 'external.three', '2.5', external_path=self.external_three_path, mod_content={'key':'replacement'}) build_package(self.loader, 'only.files', resources={ 'one':'one.txt', 'dir.two':'dir/two.txt', 'dir.three':'dir/three.txt', }) build_package(self.loader, 'mod.and.files', mod_content=[ 'from boopak import package', 'package.bexport("one")', 'package.bexport("dir")', 'package.bexport("alt.four")', ], resources={ 'zero':'zero.txt', 'one':'one.txt', 'dir.two':'dir/two.txt', 'dir.three':'dir/three.txt', 'alt.four':'alt/four.txt', 'alt.five':'alt/five.txt', }) build_package(self.loader, 'depend.one', '1', mod_content={'key':11}) build_package(self.loader, 'depend.one', '2', mod_content={'key':12}) build_package(self.loader, 'depend.two', depends=('depend.one', '1'), mod_content={'key':21}) build_package(self.loader, 'depend.two', '2', depends=('depend.one', '2'), mod_content={'key':22}) build_package(self.loader, 'depend.three', depends=('depend.two', None), mod_content={'key':3}) build_package(self.loader, 'self.depend', '1', depends=('self.depend', '1'), mod_content={'key':1}) build_package(self.loader, 'self.depend', '2', depends=('self.depend', '1'), mod_content={'key':2}) build_package(self.loader, 'mutual.depend.one', depends=('mutual.depend.two', None), mod_content={'key':1}) build_package(self.loader, 'mutual.depend.two', depends=('mutual.depend.one', None), mod_content={'key':2}) build_package(self.loader, 'depend.on.fail', depends=('external.one', None), mod_content={'key':1}) build_package(self.loader, 'depend.on.cases', depends=[ ('depend.one', None), ('missing.nospec', None), ('missing.spec', version.VersionSpec('2')), ('missing.num', version.VersionNumber('3')), ]) build_package(self.loader, 'unicode.metadata', metadata={ 'test.plain':'result', 'test.unicode':'alpha is \xce\xb1', }) build_package(self.loader, 'mixin.static', resources={ 'zero':'zero.txt', 'one':'one.txt', 'two':'two.txt', 'mixer': { 'dc.title':'Mix-In' }, }, mod_content=[ 'from boopak.package import bexport', 'bexport()', 'from boodle.sample import MixIn', 'class mixer(MixIn):', ' ranges = [', ' MixIn.range(2.0, 2.1, two, volume=1.4),', ' MixIn.range(1.5, one, pitch=1.3),', ' MixIn.range(1.0, zero),', ' ]', ' default = MixIn.default(zero)', ]) build_package(self.loader, 'mixin.file', resources={ 'zero':'zero.txt', 'one':'one.txt', 'two':'two.txt', 'mixer':('mixer.mixin', """ # Comment. range 2 2.1 two - 1.4 range - 1.5 one 1.3 range - 1.0 zero else two """), })
def validate_metadata(self): """validate_metadata() -> None Make sure that the metadata object attached to this package correctly describes the package. Also loads up various fields with information from the metadata object. Also checks that the resource tree has a valid shape. If anything is discovered to be wrong, this raises PackageLoadError. This is called by the package loader (and nothing else should call it). """ pkgname = self.name metadata = self.metadata val = metadata.get_one('boodler.package') if (not val): raise PackageLoadError(pkgname, 'no boodler.package metadata entry') if (val != pkgname): raise PackageLoadError( pkgname, 'boodler.package does not match package location: ' + val) val = metadata.get_one('boodler.version') if (not val): val = '(missing, 1.0 assumed)' vers = version.VersionNumber() else: vers = version.VersionNumber(val) if (vers != self.version): raise PackageLoadError( pkgname, 'boodler.version does not match package version: ' + val) val = metadata.get_one('boodler.main') if (not val): pass elif (val == '.'): pass elif (ident_name_regexp.match(val)): pass else: raise PackageLoadError(pkgname, 'boodler.main is not a module or . :' + val) val = metadata.get_one('boodler.api_required') if (val): spec = version.VersionSpec(val) if (self.loader.boodler_api_vers): if (not spec.match(self.loader.boodler_api_vers)): raise PackageLoadError( pkgname, 'boodler.api_required does not match Boodler version: ' + val) for val in metadata.get_all('boodler.requires'): try: pos = val.find(' ') if (pos < 0): deppkg = val depspec = None else: deppkg = val[:pos].strip() depspec = val[pos + 1:].strip() depspec = version.VersionSpec(depspec) parse_package_name(deppkg) deppkg = str(deppkg) self.dependencies.add((deppkg, depspec)) except ValueError, ex: raise PackageLoadError(pkgname, 'boodler.requires line invalid: ' + val)