class Package(PackageBaseResourceWrapper): """A package. Note: Do not instantiate this class directly, instead use the function `iter_packages` or `PackageFamily.iter_packages`. """ keys = schema_keys(package_schema) def __init__(self, resource): _check_class(resource, PackageResource) super(Package, self).__init__(resource) @cached_property def qualified_name(self): """Get the qualified name of the package. Returns: str: Name of the package with version, eg "maya-2016.1". """ o = VersionedObject.construct(self.name, self.version) return str(o) @cached_property def parent(self): """Get the parent package family. Returns: `PackageFamily`. """ repo = self.resource._repository family = repo.get_parent_package_family(self.resource) return PackageFamily(family) if family else None @cached_property def num_variants(self): return len(self.data.get("variants", [])) def iter_variants(self): """Iterate over the variants within this package, in index order. Returns: `Variant` iterator. """ repo = self.resource._repository for variant in repo.iter_variants(self.resource): yield Variant(variant) def get_variant(self, index=None): """Get the variant with the associated index. Returns: `Variant` object, or None if no variant with the given index exists. """ for variant in self.iter_variants(): if variant.index == index: return variant
class PackageFamily(PackageRepositoryResourceWrapper): """A package family. Note: Do not instantiate this class directly, instead use the function `iter_package_families`. """ keys = schema_keys(package_family_schema) def __init__(self, resource): _check_class(resource, PackageFamilyResource) super(PackageFamily, self).__init__(resource) def iter_packages(self): """Iterate over the packages within this family, in no particular order. Returns: `Package` iterator. """ for package in self.repository.iter_packages(self.resource): yield Package(package)
class Variant(PackageBaseResourceWrapper): """A package variant. Note: Do not instantiate this class directly, instead use the function `Package.iter_variants`. """ keys = schema_keys(variant_schema) keys.update(["index", "root", "subpath"]) def __init__(self, resource): _check_class(resource, VariantResource) super(Variant, self).__init__(resource) @cached_property def qualified_package_name(self): o = VersionedObject.construct(self.name, self.version) return str(o) @cached_property def qualified_name(self): """Get the qualified name of the variant. Returns: str: Name of the variant with version and index, eg "maya-2016.1[1]". """ idxstr = '' if self.index is None else str(self.index) return "%s[%s]" % (self.qualified_package_name, idxstr) @cached_property def parent(self): """Get the parent package. Returns: `Package`. """ repo = self.resource._repository package = repo.get_parent_package(self.resource) return Package(package) def get_requires(self, build_requires=False, private_build_requires=False): """Get the requirements of the variant. Args: build_requires (bool): If True, include build requirements. private_build_requires (bool): If True, include private build requirements. Returns: List of `Requirement` objects. """ requires = self.requires or [] if build_requires: requires = requires + (self.build_requires or []) if private_build_requires: requires = requires + (self.private_build_requires or []) return requires def install(self, path, dry_run=False, overrides=None): """Install this variant into another package repository. If the package already exists, this variant will be correctly merged into the package. If the variant already exists in this package, the existing variant is returned. Args: path (str): Path to destination package repository. dry_run (bool): If True, do not actually install the variant. In this mode, a `Variant` instance is only returned if the equivalent variant already exists in this repository; otherwise, None is returned. overrides (dict): Use this to change or add attributes to the installed variant. Returns: `Variant` object - the (existing or newly created) variant in the specified repository. If `dry_run` is True, None may be returned. """ repo = package_repository_manager.get_repository(path) resource = repo.install_variant(self.resource, dry_run=dry_run, overrides=overrides) if resource is None: return None elif resource is self.resource: return self else: return Variant(resource)
class Package(PackageBaseResourceWrapper): """A package. Note: Do not instantiate this class directly, instead use the function `iter_packages` or `PackageFamily.iter_packages`. """ keys = schema_keys(package_schema) def __init__(self, resource): _check_class(resource, PackageResource) super(Package, self).__init__(resource) # arbitrary keys def __getattr__(self, name): if name in self.data: return self.data[name] else: raise AttributeError("Package instance has no attribute '%s'" % name) def arbitrary_keys(self): """Get the arbitrary keys present in this package. These are any keys not in the standard list ('name', 'version' etc). Returns: set of str: Arbitrary keys. """ return set(self.data.keys()) - set(self.keys) @cached_property def qualified_name(self): """Get the qualified name of the package. Returns: str: Name of the package with version, eg "maya-2016.1". """ o = VersionedObject.construct(self.name, self.version) return str(o) @cached_property def parent(self): """Get the parent package family. Returns: `PackageFamily`. """ repo = self.resource._repository family = repo.get_parent_package_family(self.resource) return PackageFamily(family) if family else None @cached_property def num_variants(self): return len(self.data.get("variants", [])) def iter_variants(self): """Iterate over the variants within this package, in index order. Returns: `Variant` iterator. """ repo = self.resource._repository for variant in repo.iter_variants(self.resource): yield Variant(variant) def get_variant(self, index=None): """Get the variant with the associated index. Returns: `Variant` object, or None if no variant with the given index exists. """ for variant in self.iter_variants(): if variant.index == index: return variant
class VariantResourceHelper(VariantResource): """Helper class for implementing variants that inherit properties from their parent package. Since a variant overlaps so much with a package, here we use the forwarding metaclass to forward our parent package's attributes onto ourself (with some exceptions - eg 'variants', 'requires'). This is a common enough pattern that it's supplied here for other repository plugins to use. """ class _Metas(AttributeForwardMeta, LazyAttributeMeta): pass __metaclass__ = _Metas # Note: lazy key validation doesn't happen in this class, it just fowards on # attributes from the package. But LazyAttributeMeta does still use this # schema to create other class attributes, such as `validate_data`. schema = variant_schema # forward Package attributes onto ourself keys = schema_keys(package_schema) - set(["variants"]) def _uri(self): index = self.index idxstr = '' if index is None else str(index) return "%s[%s]" % (self.parent.uri, idxstr) def _subpath(self): if self.index is None: return None else: reqs = self.variant_requires dirs = [x.safe_str() for x in reqs] subpath = os.path.join(*dirs) return subpath def _root(self): if self.base is None: return None elif self.index is None: return self.base else: root = os.path.join(self.base, self.subpath) return root @cached_property def variant_requires(self): index = self.index if index is None: return [] else: try: return self.parent.variants[index] or [] except (IndexError, TypeError): raise ResourceError( "Unexpected error - variant %s cannot be found in its " "parent package %s" % (self.uri, self.parent.uri)) @property def wrapped(self): # forward Package attributes onto ourself return self.parent def _load(self): # doesn't have its own data, forwards on from parent instead return None
Optional('uuid'): basestring, Optional('config'): dict, Optional('tools'): [basestring], Optional('help'): help_schema, Optional('pre_commands'): _commands_schema, Optional('commands'): _commands_schema, Optional('post_commands'): _commands_schema, Optional('custom'): dict, Optional(basestring): object # allows deprecated fields }) package_schema_keys = schema_keys(package_schema) class PackageMaker(AttrDictWrapper): """Utility class for creating packages.""" def __init__(self, name, data=None): """Create a package maker. Args: name (str): Package name. """ super(PackageMaker, self).__init__(data) self.name = name def get_package(self): """Create the analogous package.
class Variant(PackageBaseResourceWrapper): """A package variant. Note: Do not instantiate this class directly, instead use the function `Package.iter_variants`. """ keys = schema_keys(variant_schema) keys.update(["index", "root", "subpath"]) # See comment in `Package` is_package = False is_variant = True def __init__(self, resource, context=None, parent=None): _check_class(resource, VariantResource) super(Variant, self).__init__(resource, context) self._parent = parent # arbitrary keys def __getattr__(self, name): try: return self.parent.__getattr__(name) except AttributeError: raise AttributeError("Variant instance has no attribute '%s'" % name) def arbitrary_keys(self): return self.parent.arbitrary_keys() @cached_property def qualified_package_name(self): o = VersionedObject.construct(self.name, self.version) return str(o) @cached_property def qualified_name(self): """Get the qualified name of the variant. Returns: str: Name of the variant with version and index, eg "maya-2016.1[1]". """ idxstr = '' if self.index is None else str(self.index) return "%s[%s]" % (self.qualified_package_name, idxstr) @cached_property def parent(self): """Get the parent package. Returns: `Package`. """ if self._parent is not None: return self._parent try: package = self.repository.get_parent_package(self.resource) self._parent = Package(package, context=self.context) except AttributeError as e: reraise(e, ValueError) return self._parent @property def variant_requires(self): """Get the subset of requirements specific to this variant. Returns: List of `Requirement` objects. """ if self.index is None: return [] else: return self.parent.variants[self.index] or [] @property def requires(self): """Get variant requirements. This is a concatenation of the package requirements and those of this specific variant. Returns: List of `Requirement` objects. """ return ((self.parent.requires or []) + self.variant_requires) def get_requires(self, build_requires=False, private_build_requires=False): """Get the requirements of the variant. Args: build_requires (bool): If True, include build requirements. private_build_requires (bool): If True, include private build requirements. Returns: List of `Requirement` objects. """ requires = self.requires or [] if build_requires: requires = requires + (self.build_requires or []) if private_build_requires: requires = requires + (self.private_build_requires or []) return requires def install(self, path, dry_run=False, overrides=None): """Install this variant into another package repository. If the package already exists, this variant will be correctly merged into the package. If the variant already exists in this package, the existing variant is returned. Args: path (str): Path to destination package repository. dry_run (bool): If True, do not actually install the variant. In this mode, a `Variant` instance is only returned if the equivalent variant already exists in this repository; otherwise, None is returned. overrides (dict): Use this to change or add attributes to the installed variant. Returns: `Variant` object - the (existing or newly created) variant in the specified repository. If `dry_run` is True, None may be returned. """ repo = package_repository_manager.get_repository(path) resource = repo.install_variant(self.resource, dry_run=dry_run, overrides=overrides) if resource is None: return None elif resource is self.resource: return self else: return Variant(resource) @property def _non_shortlinked_subpath(self): return self.resource._subpath(ignore_shortlinks=True)
class Package(PackageBaseResourceWrapper): """A package. Note: Do not instantiate this class directly, instead use the function `iter_packages` or `PackageFamily.iter_packages`. """ keys = schema_keys(package_schema) # This is to allow for a simple check like 'this.is_package' in late-bound # funcs, where 'this' may be a package or variant. # is_package = True is_variant = False def __init__(self, resource, context=None): _check_class(resource, PackageResource) super(Package, self).__init__(resource, context) # arbitrary keys def __getattr__(self, name): if name in self.data: value = self.data[name] return self._wrap_forwarded(name, value) else: raise AttributeError("Package instance has no attribute '%s'" % name) def arbitrary_keys(self): """Get the arbitrary keys present in this package. These are any keys not in the standard list ('name', 'version' etc). Returns: set of str: Arbitrary keys. """ return set(self.data.keys()) - set(self.keys) @cached_property def qualified_name(self): """Get the qualified name of the package. Returns: str: Name of the package with version, eg "maya-2016.1". """ o = VersionedObject.construct(self.name, self.version) return str(o) def as_exact_requirement(self): """Get the package, as an exact requirement string. Returns: Equivalent requirement string, eg "maya==2016.1" """ o = VersionedObject.construct(self.name, self.version) return o.as_exact_requirement() @cached_property def parent(self): """Get the parent package family. Returns: `PackageFamily`. """ family = self.repository.get_parent_package_family(self.resource) return PackageFamily(family) if family else None @cached_property def num_variants(self): return len(self.data.get("variants", [])) @property def is_relocatable(self): """True if the package and its payload is safe to copy. """ if self.relocatable is not None: return self.relocatable if config.default_relocatable_per_repository: value = config.default_relocatable_per_repository.get( self.repository.location) if value is not None: return value if config.default_relocatable_per_package: value = config.default_relocatable_per_package.get(self.name) if value is not None: return value return config.default_relocatable @property def is_cachable(self): """True if the package and its payload is safe to cache locally. """ if self.cachable is not None: return self.cachable if config.default_cachable_per_repository: # TODO: The location of filesystem repository is canonical path, # so if the path in `default_cachable_per_repository` isn't # canonical, this may return false value e.g. on Windows. value = config.default_cachable_per_repository.get( self.repository.location) if value is not None: return value if config.default_cachable_per_package: value = config.default_cachable_per_package.get(self.name) if value is not None: return value if config.default_cachable is not None: return config.default_cachable return self.is_relocatable def iter_variants(self): """Iterate over the variants within this package, in index order. Returns: `Variant` iterator. """ for variant in self.repository.iter_variants(self.resource): yield Variant(variant, context=self.context, parent=self) def get_variant(self, index=None): """Get the variant with the associated index. Returns: `Variant` object, or None if no variant with the given index exists. """ for variant in self.iter_variants(): if variant.index == index: return variant
class VariantResourceHelper(six.with_metaclass(_Metas, VariantResource)): """Helper class for implementing variants that inherit properties from their parent package. Since a variant overlaps so much with a package, here we use the forwarding metaclass to forward our parent package's attributes onto ourself (with some exceptions - eg 'variants', 'requires'). This is a common enough pattern that it's supplied here for other repository plugins to use. """ # Note: lazy key validation doesn't happen in this class, it just fowards on # attributes from the package. But LazyAttributeMeta does still use this # schema to create other class attributes, such as `validate_data`. schema = variant_schema # forward Package attributes onto ourself keys = schema_keys(package_schema) - set(["variants"]) def _uri(self): index = self.index idxstr = '' if index is None else str(index) return "%s[%s]" % (self.parent.uri, idxstr) def _subpath(self, ignore_shortlinks=False): if self.index is None: return None if self.parent.hashed_variants: vars_str = str(list(map(str, self.variant_requires))) h = sha1(vars_str.encode("utf8")) hashdir = h.hexdigest() if (not ignore_shortlinks) and \ config.use_variant_shortlinks and \ self.base is not None: # search for matching shortlink and use that path = os.path.join(self.base, config.variant_shortlinks_dirname) if os.path.exists(path): actual_root = os.path.join(self.base, hashdir) linkname = find_matching_symlink(path, actual_root) if linkname: return os.path.join(config.variant_shortlinks_dirname, linkname) return hashdir else: dirs = [x.safe_str() for x in self.variant_requires] subpath = os.path.join(*dirs) return subpath def _root(self, ignore_shortlinks=False): if self.base is None: return None elif self.index is None: return self.base else: subpath = self._subpath(ignore_shortlinks=ignore_shortlinks) root = os.path.join(self.base, subpath) return root @cached_property def variant_requires(self): index = self.index if index is None: return [] else: try: return self.parent.variants[index] or [] except (IndexError, TypeError): raise ResourceError( "Unexpected error - variant %s cannot be found in its " "parent package %s" % (self.uri, self.parent.uri)) @property def wrapped(self): # forward Package attributes onto ourself return self.parent def _load(self): # doesn't have its own data, forwards on from parent instead return None