class Dependency(PackageSpecification): def __init__( self, name, # type: str constraint, # type: Union[str, VersionConstraint] optional=False, # type: bool category="main", # type: str allows_prereleases=False, # type: bool extras=None, # type: Union[List[str], FrozenSet[str]] source_type=None, # type: Optional[str] source_url=None, # type: Optional[str] source_reference=None, # type: Optional[str] source_resolved_reference=None, # type: Optional[str] ): super(Dependency, self).__init__( name, source_type=source_type, source_url=source_url, source_reference=source_reference, source_resolved_reference=source_resolved_reference, features=extras, ) try: if not isinstance(constraint, VersionConstraint): self._constraint = parse_constraint(constraint) else: self._constraint = constraint except ValueError: self._constraint = parse_constraint("*") self._pretty_constraint = str(constraint) self._optional = optional self._category = category if isinstance(self._constraint, VersionRange) and self._constraint.min: allows_prereleases = ( allows_prereleases or self._constraint.min.is_prerelease() ) self._allows_prereleases = allows_prereleases self._python_versions = "*" self._python_constraint = parse_constraint("*") self._transitive_python_versions = None self._transitive_python_constraint = None self._transitive_marker = None self._extras = frozenset(extras or []) self._in_extras = [] self._activated = not self._optional self.is_root = False self.marker = AnyMarker() self.source_name = None @property def name(self): return self._name @property def constraint(self): return self._constraint @property def pretty_constraint(self): return self._pretty_constraint @property def pretty_name(self): return self._pretty_name @property def category(self): return self._category @property def python_versions(self): return self._python_versions @python_versions.setter def python_versions(self, value): self._python_versions = value self._python_constraint = parse_constraint(value) if not self._python_constraint.is_any(): self.marker = self.marker.intersect( parse_marker( self._create_nested_marker( "python_version", self._python_constraint ) ) ) @property def transitive_python_versions(self): if self._transitive_python_versions is None: return self._python_versions return self._transitive_python_versions @transitive_python_versions.setter def transitive_python_versions(self, value): self._transitive_python_versions = value self._transitive_python_constraint = parse_constraint(value) @property def transitive_marker(self): if self._transitive_marker is None: return self.marker return self._transitive_marker @transitive_marker.setter def transitive_marker(self, value): self._transitive_marker = value @property def python_constraint(self): return self._python_constraint @property def transitive_python_constraint(self): if self._transitive_python_constraint is None: return self._python_constraint return self._transitive_python_constraint @property def extras(self): # type: () -> FrozenSet[str] return self._extras @property def in_extras(self): # type: () -> list return self._in_extras @property def base_pep_508_name(self): # type: () -> str requirement = self.pretty_name if self.extras: requirement += "[{}]".format(",".join(self.extras)) if isinstance(self.constraint, VersionUnion): if self.constraint.excludes_single_version(): requirement += " ({})".format(str(self.constraint)) else: constraints = self.pretty_constraint.split(",") constraints = [parse_constraint(c) for c in constraints] constraints = [str(c) for c in constraints] requirement += " ({})".format(",".join(constraints)) elif isinstance(self.constraint, Version): requirement += " (=={})".format(self.constraint.text) elif not self.constraint.is_any(): requirement += " ({})".format(str(self.constraint).replace(" ", "")) return requirement def allows_prereleases(self): return self._allows_prereleases def is_optional(self): return self._optional def is_activated(self): return self._activated def is_vcs(self): return False def is_file(self): return False def is_directory(self): return False def is_url(self): return False def accepts(self, package): # type: (poetry.core.packages.Package) -> bool """ Determines if the given package matches this dependency. """ return ( self._name == package.name and self._constraint.allows(package.version) and (not package.is_prerelease() or self.allows_prereleases()) ) def to_pep_508(self, with_extras=True): # type: (bool) -> str requirement = self.base_pep_508_name markers = [] has_extras = False if not self.marker.is_any(): marker = self.marker if not with_extras: marker = marker.without_extras() if not marker.is_empty(): markers.append(str(marker)) has_extras = "extra" in convert_markers(marker) else: # Python marker if self.python_versions != "*": python_constraint = self.python_constraint markers.append( self._create_nested_marker("python_version", python_constraint) ) in_extras = " || ".join(self._in_extras) if in_extras and with_extras and not has_extras: markers.append( self._create_nested_marker("extra", parse_generic_constraint(in_extras)) ) if markers: if self.is_vcs() or self.is_url(): requirement += " " if len(markers) > 1: markers = ["({})".format(m) for m in markers] requirement += "; {}".format(" and ".join(markers)) else: requirement += "; {}".format(markers[0]) return requirement def _create_nested_marker(self, name, constraint): if isinstance(constraint, (MultiConstraint, UnionConstraint)): parts = [] for c in constraint.constraints: multi = False if isinstance(c, (MultiConstraint, UnionConstraint)): multi = True parts.append((multi, self._create_nested_marker(name, c))) glue = " and " if isinstance(constraint, UnionConstraint): parts = [ "({})".format(part[1]) if part[0] else part[1] for part in parts ] glue = " or " else: parts = [part[1] for part in parts] marker = glue.join(parts) elif isinstance(constraint, Constraint): marker = '{} {} "{}"'.format(name, constraint.operator, constraint.version) elif isinstance(constraint, VersionUnion): parts = [] for c in constraint.ranges: parts.append(self._create_nested_marker(name, c)) glue = " or " parts = ["({})".format(part) for part in parts] marker = glue.join(parts) elif isinstance(constraint, Version): if constraint.precision >= 3 and name == "python_version": name = "python_full_version" marker = '{} == "{}"'.format(name, constraint.text) else: if constraint.min is not None: min_name = name if constraint.min.precision >= 3 and name == "python_version": min_name = "python_full_version" if constraint.max is None: name = min_name op = ">=" if not constraint.include_min: op = ">" version = constraint.min.text if constraint.max is not None: max_name = name if constraint.max.precision >= 3 and name == "python_version": max_name = "python_full_version" text = '{} {} "{}"'.format(min_name, op, version) op = "<=" if not constraint.include_max: op = "<" version = constraint.max text += ' and {} {} "{}"'.format(max_name, op, version) return text elif constraint.max is not None: if constraint.max.precision >= 3 and name == "python_version": name = "python_full_version" op = "<=" if not constraint.include_max: op = "<" version = constraint.max else: return "" marker = '{} {} "{}"'.format(name, op, version) return marker def activate(self): """ Set the dependency as mandatory. """ self._activated = True def deactivate(self): """ Set the dependency as optional. """ if not self._optional: self._optional = True self._activated = False def with_constraint(self, constraint): new = Dependency( self.pretty_name, constraint, optional=self.is_optional(), category=self.category, allows_prereleases=self.allows_prereleases(), extras=self._extras, source_type=self._source_type, source_url=self._source_url, source_reference=self._source_reference, ) new.is_root = self.is_root new.python_versions = self.python_versions for in_extra in self.in_extras: new.in_extras.append(in_extra) return new def __eq__(self, other): if not isinstance(other, Dependency): return NotImplemented return ( self.is_same_package_as(other) and self._constraint == other.constraint and self._extras == other.extras ) def __ne__(self, other): return not self == other def __hash__(self): return ( super(Dependency, self).__hash__() ^ hash(self._constraint) ^ hash(self._extras) ) def __str__(self): if self.is_root: return self._pretty_name name = self._pretty_name if self._features: name = "{}[{}]".format(name, ",".join(sorted(self._features))) return "{} ({})".format(name, self._pretty_constraint) def __repr__(self): return "<{} {}>".format(self.__class__.__name__, str(self))
class Package(object): AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7", "3.8"} def __init__(self, name, version, pretty_version=None): """ Creates a new in memory package. """ self._pretty_name = name self._name = canonicalize_name(name) if not isinstance(version, Version): self._version = Version.parse(version) self._pretty_version = pretty_version or version else: self._version = version self._pretty_version = pretty_version or self._version.text self.description = "" self._authors = [] self._maintainers = [] self.homepage = None self.repository_url = None self.documentation_url = None self.keywords = [] self._license = None self.readme = None self.source_name = "" self.source_type = "" self.source_reference = "" self.source_url = "" self.requires = [] self.dev_requires = [] self.extras = {} self.requires_extras = [] self.category = "main" self.files = [] self.optional = False self.classifiers = [] self._python_versions = "*" self._python_constraint = parse_constraint("*") self._python_marker = AnyMarker() self.platform = None self.marker = AnyMarker() self.root_dir = None self.develop = True @property def name(self): return self._name @property def pretty_name(self): return self._pretty_name @property def version(self): return self._version @property def pretty_version(self): return self._pretty_version @property def unique_name(self): if self.is_root(): return self._name return self.name + "-" + self._version.text @property def pretty_string(self): return self.pretty_name + " " + self.pretty_version @property def full_pretty_version(self): if self.source_type in ["file", "directory", "url"]: return "{} {}".format(self._pretty_version, self.source_url) if self.source_type not in ["hg", "git"]: return self._pretty_version # if source reference is a sha1 hash -- truncate if len(self.source_reference) == 40: return "{} {}".format(self._pretty_version, self.source_reference[0:7]) return "{} {}".format(self._pretty_version, self.source_reference) @property def authors(self): # type: () -> list return self._authors @property def author_name(self): # type: () -> str return self._get_author()["name"] @property def author_email(self): # type: () -> str return self._get_author()["email"] @property def maintainers(self): # type: () -> list return self._maintainers @property def maintainer_name(self): # type: () -> str return self._get_maintainer()["name"] @property def maintainer_email(self): # type: () -> str return self._get_maintainer()["email"] @property def all_requires(self): return self.requires + self.dev_requires def _get_author(self): # type: () -> dict if not self._authors: return {"name": None, "email": None} m = AUTHOR_REGEX.match(self._authors[0]) name = m.group("name") email = m.group("email") return {"name": name, "email": email} def _get_maintainer(self): # type: () -> dict if not self._maintainers: return {"name": None, "email": None} m = AUTHOR_REGEX.match(self._maintainers[0]) name = m.group("name") email = m.group("email") return {"name": name, "email": email} @property def python_versions(self): return self._python_versions @python_versions.setter def python_versions(self, value): self._python_versions = value self._python_constraint = parse_constraint(value) self._python_marker = parse_marker( create_nested_marker("python_version", self._python_constraint)) @property def python_constraint(self): return self._python_constraint @property def python_marker(self): return self._python_marker @property def license(self): return self._license @license.setter def license(self, value): if value is None: self._license = value elif isinstance(value, License): self._license = value else: self._license = license_by_id(value) @property def all_classifiers(self): classifiers = copy.copy(self.classifiers) # Automatically set python classifiers if self.python_versions == "*": python_constraint = parse_constraint("~2.7 || ^3.4") else: python_constraint = self.python_constraint for version in sorted(self.AVAILABLE_PYTHONS): if len(version) == 1: constraint = parse_constraint(version + ".*") else: constraint = Version.parse(version) if python_constraint.allows_any(constraint): classifiers.append( "Programming Language :: Python :: {}".format(version)) # Automatically set license classifiers if self.license: classifiers.append(self.license.classifier) classifiers = set(classifiers) return sorted(classifiers) @property def urls(self): urls = {} if self.homepage: urls["Homepage"] = self.homepage if self.repository_url: urls["Repository"] = self.repository_url if self.documentation_url: urls["Documentation"] = self.documentation_url return urls def is_prerelease(self): return self._version.is_prerelease() def is_root(self): return False def add_dependency( self, name, # type: str constraint=None, # type: Union[str, dict, None] category="main", # type: str ): # type: (...) -> Dependency if constraint is None: constraint = "*" if isinstance(constraint, dict): optional = constraint.get("optional", False) python_versions = constraint.get("python") platform = constraint.get("platform") markers = constraint.get("markers") if "allows-prereleases" in constraint: message = ( 'The "{}" dependency specifies ' 'the "allows-prereleases" property, which is deprecated. ' 'Use "allow-prereleases" instead.'.format(name)) warn(message, DeprecationWarning) logger.warning(message) allows_prereleases = constraint.get( "allow-prereleases", constraint.get("allows-prereleases", False)) if "git" in constraint: # VCS dependency dependency = VCSDependency( name, "git", constraint["git"], branch=constraint.get("branch", None), tag=constraint.get("tag", None), rev=constraint.get("rev", None), category=category, optional=optional, develop=constraint.get("develop", True), ) elif "file" in constraint: file_path = Path(constraint["file"]) dependency = FileDependency(name, file_path, category=category, base=self.root_dir) elif "path" in constraint: path = Path(constraint["path"]) if self.root_dir: is_file = (self.root_dir / path).is_file() else: is_file = path.is_file() if is_file: dependency = FileDependency( name, path, category=category, optional=optional, base=self.root_dir, ) else: dependency = DirectoryDependency( name, path, category=category, optional=optional, base=self.root_dir, develop=constraint.get("develop", True), ) elif "url" in constraint: dependency = URLDependency(name, constraint["url"], category=category, optional=optional) else: version = constraint["version"] dependency = Dependency( name, version, optional=optional, category=category, allows_prereleases=allows_prereleases, source_name=constraint.get("source"), ) if not markers: marker = AnyMarker() if python_versions: dependency.python_versions = python_versions marker = marker.intersect( parse_marker( create_nested_marker( "python_version", dependency.python_constraint))) if platform: marker = marker.intersect( parse_marker( create_nested_marker( "sys_platform", parse_generic_constraint(platform)))) else: marker = parse_marker(markers) if not marker.is_any(): dependency.marker = marker if "extras" in constraint: for extra in constraint["extras"]: dependency.extras.append(extra) else: dependency = Dependency(name, constraint, category=category) if category == "dev": self.dev_requires.append(dependency) else: self.requires.append(dependency) return dependency def to_dependency(self): from . import dependency_from_pep_508 name = "{} (=={})".format(self._name, self._version) if not self.marker.is_any(): name += " ; {}".format(str(self.marker)) return dependency_from_pep_508(name) @contextmanager def with_python_versions(self, python_versions): original_python_versions = self.python_versions self.python_versions = python_versions yield self.python_versions = original_python_versions def clone(self): # type: () -> Package clone = self.__class__(self.pretty_name, self.version) clone.category = self.category clone.optional = self.optional clone.python_versions = self.python_versions clone.marker = self.marker clone.extras = self.extras clone.source_type = self.source_type clone.source_url = self.source_url clone.source_reference = self.source_reference for dep in self.requires: clone.requires.append(dep) for dep in self.dev_requires: clone.dev_requires.append(dep) return clone def __hash__(self): return hash((self._name, self._version)) def __eq__(self, other): if not isinstance(other, Package): return NotImplemented return self._name == other.name and self._version == other.version def __str__(self): return self.unique_name def __repr__(self): return "<Package {}>".format(self.unique_name)
def add_dependency( self, name, # type: str constraint=None, # type: Union[str, dict, None] category="main", # type: str ): # type: (...) -> Dependency if constraint is None: constraint = "*" if isinstance(constraint, dict): optional = constraint.get("optional", False) python_versions = constraint.get("python") platform = constraint.get("platform") markers = constraint.get("markers") if "allows-prereleases" in constraint: message = ( 'The "{}" dependency specifies ' 'the "allows-prereleases" property, which is deprecated. ' 'Use "allow-prereleases" instead.'.format(name)) warn(message, DeprecationWarning) logger.warning(message) allows_prereleases = constraint.get( "allow-prereleases", constraint.get("allows-prereleases", False)) if "git" in constraint: # VCS dependency dependency = VCSDependency( name, "git", constraint["git"], branch=constraint.get("branch", None), tag=constraint.get("tag", None), rev=constraint.get("rev", None), category=category, optional=optional, develop=constraint.get("develop", True), ) elif "file" in constraint: file_path = Path(constraint["file"]) dependency = FileDependency(name, file_path, category=category, base=self.root_dir) elif "path" in constraint: path = Path(constraint["path"]) if self.root_dir: is_file = (self.root_dir / path).is_file() else: is_file = path.is_file() if is_file: dependency = FileDependency( name, path, category=category, optional=optional, base=self.root_dir, ) else: dependency = DirectoryDependency( name, path, category=category, optional=optional, base=self.root_dir, develop=constraint.get("develop", True), ) elif "url" in constraint: dependency = URLDependency(name, constraint["url"], category=category, optional=optional) else: version = constraint["version"] dependency = Dependency( name, version, optional=optional, category=category, allows_prereleases=allows_prereleases, source_name=constraint.get("source"), ) if not markers: marker = AnyMarker() if python_versions: dependency.python_versions = python_versions marker = marker.intersect( parse_marker( create_nested_marker( "python_version", dependency.python_constraint))) if platform: marker = marker.intersect( parse_marker( create_nested_marker( "sys_platform", parse_generic_constraint(platform)))) else: marker = parse_marker(markers) if not marker.is_any(): dependency.marker = marker if "extras" in constraint: for extra in constraint["extras"]: dependency.extras.append(extra) else: dependency = Dependency(name, constraint, category=category) if category == "dev": self.dev_requires.append(dependency) else: self.requires.append(dependency) return dependency
class Package(PackageSpecification): AVAILABLE_PYTHONS = { "2", "2.7", "3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", } def __init__( self, name: str, version: Union[str, "Version"], pretty_version: Optional[str] = None, source_type: Optional[str] = None, source_url: Optional[str] = None, source_reference: Optional[str] = None, source_resolved_reference: Optional[str] = None, source_subdirectory: Optional[str] = None, features: Optional[List[str]] = None, develop: bool = False, ) -> None: """ Creates a new in memory package. """ from poetry.core.semver.version import Version from poetry.core.version.markers import AnyMarker super(Package, self).__init__( name, source_type=source_type, source_url=source_url, source_reference=source_reference, source_resolved_reference=source_resolved_reference, source_subdirectory=source_subdirectory, features=features, ) if not isinstance(version, Version): self._version = Version.parse(version) self._pretty_version = pretty_version or version else: self._version = version self._pretty_version = pretty_version or self._version.text self.description = "" self._authors = [] self._maintainers = [] self.homepage = None self.repository_url = None self.documentation_url = None self.keywords = [] self._license = None self.readme = None self.extras = {} self.requires_extras = [] self._dependency_groups: Dict[str, "DependencyGroup"] = {} # For compatibility with previous version, we keep the category self.category = "main" self.files = [] self.optional = False self.classifiers = [] self._python_versions = "*" self._python_constraint = parse_constraint("*") self._python_marker = AnyMarker() self.platform = None self.marker = AnyMarker() self.root_dir = None self.develop = develop @property def name(self) -> str: return self._name @property def pretty_name(self) -> str: return self._pretty_name @property def version(self) -> "Version": return self._version @property def pretty_version(self) -> str: return self._pretty_version @property def unique_name(self) -> str: if self.is_root(): return self._name return self.complete_name + "-" + self._version.text @property def pretty_string(self) -> str: return self.pretty_name + " " + self.pretty_version @property def full_pretty_version(self) -> str: if self.source_type in ["file", "directory", "url"]: return "{} {}".format(self._pretty_version, self.source_url) if self.source_type not in ["hg", "git"]: return self._pretty_version if self.source_resolved_reference: if len(self.source_resolved_reference) == 40: return "{} {}".format(self._pretty_version, self.source_resolved_reference[0:7]) # if source reference is a sha1 hash -- truncate if len(self.source_reference) == 40: return "{} {}".format(self._pretty_version, self.source_reference[0:7]) return "{} {}".format( self._pretty_version, self._source_resolved_reference or self._source_reference, ) @property def authors(self) -> List[str]: return self._authors @property def author_name(self) -> str: return self._get_author()["name"] @property def author_email(self) -> str: return self._get_author()["email"] @property def maintainers(self) -> List[str]: return self._maintainers @property def maintainer_name(self) -> str: return self._get_maintainer()["name"] @property def maintainer_email(self) -> str: return self._get_maintainer()["email"] @property def requires(self) -> List["DependencyTypes"]: """ Returns the default dependencies """ if not self._dependency_groups or "default" not in self._dependency_groups: return [] return self._dependency_groups["default"].dependencies @property def all_requires(self, ) -> List[Union["DependencyTypes"]]: """ Returns the default dependencies and group dependencies. """ return self.requires + [ dependency for group in self._dependency_groups.values() for dependency in group.dependencies if group.name != "default" ] def _get_author(self) -> Dict[str, Optional[str]]: if not self._authors: return {"name": None, "email": None} m = AUTHOR_REGEX.match(self._authors[0]) if m is None: raise ValueError("Invalid author string. Must be in the format: " "John Smith <*****@*****.**>") name = m.group("name") email = m.group("email") return {"name": name, "email": email} def _get_maintainer(self) -> Dict[str, Optional[str]]: if not self._maintainers: return {"name": None, "email": None} m = AUTHOR_REGEX.match(self._maintainers[0]) if m is None: raise ValueError( "Invalid maintainer string. Must be in the format: " "John Smith <*****@*****.**>") name = m.group("name") email = m.group("email") return {"name": name, "email": email} @property def python_versions(self) -> str: return self._python_versions @python_versions.setter def python_versions(self, value: str) -> None: self._python_versions = value self._python_constraint = parse_constraint(value) self._python_marker = parse_marker( create_nested_marker("python_version", self._python_constraint)) @property def python_constraint(self) -> "VersionTypes": return self._python_constraint @property def python_marker(self) -> "BaseMarker": return self._python_marker @property def license(self) -> "License": return self._license @license.setter def license(self, value: Optional[Union[str, "License"]]) -> None: from poetry.core.spdx.helpers import license_by_id from poetry.core.spdx.license import License # noqa if value is None: self._license = value elif isinstance(value, License): self._license = value else: self._license = license_by_id(value) @property def all_classifiers(self) -> List[str]: from poetry.core.semver.version import Version # noqa classifiers = copy.copy(self.classifiers) # Automatically set python classifiers if self.python_versions == "*": python_constraint = parse_constraint("~2.7 || ^3.4") else: python_constraint = self.python_constraint python_classifier_prefix = "Programming Language :: Python" python_classifiers = [] # we sort python versions by sorting an int tuple of (major, minor) version # to ensure we sort 3.10 after 3.9 for version in sorted(self.AVAILABLE_PYTHONS, key=lambda x: tuple(map(int, x.split(".")))): if len(version) == 1: constraint = parse_constraint(version + ".*") else: constraint = Version.parse(version) if python_constraint.allows_any(constraint): classifier = "{} :: {}".format(python_classifier_prefix, version) if classifier not in python_classifiers: python_classifiers.append(classifier) # Automatically set license classifiers if self.license: classifiers.append(self.license.classifier) # Sort classifiers and insert python classifiers at the right location. We do # it like this so that 3.10 is sorted after 3.9. sorted_classifiers = [] python_classifiers_inserted = False for classifier in sorted(set(classifiers)): if (not python_classifiers_inserted and classifier > python_classifier_prefix): sorted_classifiers.extend(python_classifiers) python_classifiers_inserted = True sorted_classifiers.append(classifier) if not python_classifiers_inserted: sorted_classifiers.extend(python_classifiers) return sorted_classifiers @property def urls(self) -> Dict[str, str]: urls = {} if self.homepage: urls["Homepage"] = self.homepage if self.repository_url: urls["Repository"] = self.repository_url if self.documentation_url: urls["Documentation"] = self.documentation_url return urls def is_prerelease(self) -> bool: return self._version.is_unstable() def is_root(self) -> bool: return False def add_dependency_group(self, group: "DependencyGroup") -> None: self._dependency_groups[group.name] = group def dependency_group(self, name: str) -> "DependencyGroup": if name not in self._dependency_groups: raise ValueError(f'The dependency group "{name}" does not exist.') return self._dependency_groups[name] def add_dependency( self, dependency: "DependencyTypes", ) -> "DependencyTypes": from .dependency_group import DependencyGroup for group_name in dependency.groups: if group_name not in self._dependency_groups: # Dynamically add the dependency group self.add_dependency_group(DependencyGroup(group_name)) self._dependency_groups[group_name].add_dependency(dependency) return dependency def without_dependency_groups(self, groups: List[str]) -> "Package": """ Returns a clone of the package with the given dependency groups excluded. """ package = self.clone() for group_name in groups: if group_name in package._dependency_groups: del package._dependency_groups[group_name] return package def without_optional_dependency_groups(self) -> "Package": """ Returns a clone of the package without optional dependency groups. """ package = self.clone() for group_name, group in self._dependency_groups.items(): if group.is_optional(): del package._dependency_groups[group_name] return package def with_dependency_groups(self, groups: List[str], only: bool = False) -> "Package": """ Returns a clone of the package with the given dependency groups opted in. Note that it will return all dependencies across all groups more the given, optional, groups. If `only` is set to True, then only the given groups will be selected. """ package = self.clone() for group_name, group in self._dependency_groups.items(): if only: if group_name not in groups: del package._dependency_groups[group_name] else: if group.is_optional() and group_name not in groups: del package._dependency_groups[group_name] return package def to_dependency(self, ) -> Union["DependencyTypes"]: from pathlib import Path from .dependency import Dependency from .directory_dependency import DirectoryDependency from .file_dependency import FileDependency from .url_dependency import URLDependency from .vcs_dependency import VCSDependency if self.source_type == "directory": dep = DirectoryDependency( self._name, Path(self._source_url), groups=list(self._dependency_groups.keys()), optional=self.optional, base=self.root_dir, develop=self.develop, extras=self.features, ) elif self.source_type == "file": dep = FileDependency( self._name, Path(self._source_url), groups=list(self._dependency_groups.keys()), optional=self.optional, base=self.root_dir, extras=self.features, ) elif self.source_type == "url": dep = URLDependency( self._name, self._source_url, groups=list(self._dependency_groups.keys()), optional=self.optional, extras=self.features, ) elif self.source_type == "git": dep = VCSDependency( self._name, self.source_type, self.source_url, rev=self.source_reference, resolved_rev=self.source_resolved_reference, directory=self.source_subdirectory, groups=list(self._dependency_groups.keys()), optional=self.optional, develop=self.develop, extras=self.features, ) else: dep = Dependency(self._name, self._version, extras=self.features) if not self.marker.is_any(): dep.marker = self.marker if not self.python_constraint.is_any(): dep.python_versions = self.python_versions if self._source_type not in ["directory", "file", "url", "git"]: return dep return dep.with_constraint(self._version) @contextmanager def with_python_versions(self, python_versions: str) -> None: original_python_versions = self.python_versions self.python_versions = python_versions yield self.python_versions = original_python_versions def with_features(self, features: List[str]) -> "Package": package = self.clone() package._features = frozenset(features) return package def without_features(self) -> "Package": return self.with_features([]) def clone(self) -> "Package": clone = self.__class__(self.pretty_name, self.version) clone.__dict__ = copy.deepcopy(self.__dict__) return clone def __hash__(self) -> int: return super(Package, self).__hash__() ^ hash(self._version) def __eq__(self, other: "Package") -> bool: if not isinstance(other, Package): return NotImplemented return self.is_same_package_as( other) and self._version == other.version def __str__(self) -> str: return "{} ({})".format(self.complete_name, self.full_pretty_version) def __repr__(self) -> str: args = [repr(self._name), repr(self._version.text)] if self._features: args.append("features={}".format(repr(self._features))) if self._source_type: args.append("source_type={}".format(repr(self._source_type))) args.append("source_url={}".format(repr(self._source_url))) if self._source_reference: args.append("source_reference={}".format( repr(self._source_reference))) if self._source_resolved_reference: args.append("source_resolved_reference={}".format( repr(self._source_resolved_reference))) return "Package({})".format(", ".join(args))
class Package(PackageSpecification): AVAILABLE_PYTHONS = { "2", "2.7", "3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", } def __init__( self, name: str, version: Union[str, "Version"], pretty_version: Optional[str] = None, source_type: Optional[str] = None, source_url: Optional[str] = None, source_reference: Optional[str] = None, source_resolved_reference: Optional[str] = None, features: Optional[List[str]] = None, # type ) -> None: """ Creates a new in memory package. """ from poetry.core.semver.version import Version from poetry.core.version.markers import AnyMarker super(Package, self).__init__( name, source_type=source_type, source_url=source_url, source_reference=source_reference, source_resolved_reference=source_resolved_reference, features=features, ) if not isinstance(version, Version): self._version = Version.parse(version) self._pretty_version = pretty_version or version else: self._version = version self._pretty_version = pretty_version or self._version.text self.description = "" self._authors = [] self._maintainers = [] self.homepage = None self.repository_url = None self.documentation_url = None self.keywords = [] self._license = None self.readme = None self.requires = [] self.dev_requires = [] self.extras = {} self.requires_extras = [] self.category = "main" self.files = [] self.optional = False self.classifiers = [] self._python_versions = "*" self._python_constraint = parse_constraint("*") self._python_marker = AnyMarker() self.platform = None self.marker = AnyMarker() self.root_dir = None self.develop = True @property def name(self) -> str: return self._name @property def pretty_name(self) -> str: return self._pretty_name @property def version(self) -> "Version": return self._version @property def pretty_version(self) -> str: return self._pretty_version @property def unique_name(self) -> str: if self.is_root(): return self._name return self.complete_name + "-" + self._version.text @property def pretty_string(self) -> str: return self.pretty_name + " " + self.pretty_version @property def full_pretty_version(self) -> str: if self.source_type in ["file", "directory", "url"]: return "{} {}".format(self._pretty_version, self.source_url) if self.source_type not in ["hg", "git"]: return self._pretty_version if self.source_resolved_reference: if len(self.source_resolved_reference) == 40: return "{} {}".format(self._pretty_version, self.source_resolved_reference[0:7]) # if source reference is a sha1 hash -- truncate if len(self.source_reference) == 40: return "{} {}".format(self._pretty_version, self.source_reference[0:7]) return "{} {}".format( self._pretty_version, self._source_resolved_reference or self._source_reference, ) @property def authors(self) -> List[str]: return self._authors @property def author_name(self) -> str: return self._get_author()["name"] @property def author_email(self) -> str: return self._get_author()["email"] @property def maintainers(self) -> List[str]: return self._maintainers @property def maintainer_name(self) -> str: return self._get_maintainer()["name"] @property def maintainer_email(self) -> str: return self._get_maintainer()["email"] @property def all_requires(self, ) -> List[Union["DependencyTypes"]]: return self.requires + self.dev_requires def _get_author(self) -> Dict[str, Optional[str]]: if not self._authors: return {"name": None, "email": None} m = AUTHOR_REGEX.match(self._authors[0]) if m is None: raise ValueError("Invalid author string. Must be in the format: " "John Smith <*****@*****.**>") name = m.group("name") email = m.group("email") return {"name": name, "email": email} def _get_maintainer(self) -> Dict[str, Optional[str]]: if not self._maintainers: return {"name": None, "email": None} m = AUTHOR_REGEX.match(self._maintainers[0]) if m is None: raise ValueError( "Invalid maintainer string. Must be in the format: " "John Smith <*****@*****.**>") name = m.group("name") email = m.group("email") return {"name": name, "email": email} @property def python_versions(self) -> str: return self._python_versions @python_versions.setter def python_versions(self, value: str) -> None: self._python_versions = value self._python_constraint = parse_constraint(value) self._python_marker = parse_marker( create_nested_marker("python_version", self._python_constraint)) @property def python_constraint(self) -> "VersionTypes": return self._python_constraint @property def python_marker(self) -> "BaseMarker": return self._python_marker @property def license(self) -> "License": return self._license @license.setter def license(self, value: Optional[Union[str, "License"]]) -> None: from poetry.core.spdx.helpers import license_by_id from poetry.core.spdx.license import License # noqa if value is None: self._license = value elif isinstance(value, License): self._license = value else: self._license = license_by_id(value) @property def all_classifiers(self) -> List[str]: from poetry.core.semver.version import Version # noqa classifiers = copy.copy(self.classifiers) # Automatically set python classifiers if self.python_versions == "*": python_constraint = parse_constraint("~2.7 || ^3.4") else: python_constraint = self.python_constraint for version in sorted(self.AVAILABLE_PYTHONS): if len(version) == 1: constraint = parse_constraint(version + ".*") else: constraint = Version.parse(version) if python_constraint.allows_any(constraint): classifiers.append( "Programming Language :: Python :: {}".format(version)) # Automatically set license classifiers if self.license: classifiers.append(self.license.classifier) classifiers = set(classifiers) return sorted(classifiers) @property def urls(self) -> Dict[str, str]: urls = {} if self.homepage: urls["Homepage"] = self.homepage if self.repository_url: urls["Repository"] = self.repository_url if self.documentation_url: urls["Documentation"] = self.documentation_url return urls def is_prerelease(self) -> bool: return self._version.is_prerelease() def is_root(self) -> bool: return False def add_dependency( self, dependency: "DependencyTypes", ) -> "DependencyTypes": if dependency.category == "dev": self.dev_requires.append(dependency) else: self.requires.append(dependency) return dependency def to_dependency(self, ) -> Union["DependencyTypes"]: from pathlib import Path from .dependency import Dependency from .directory_dependency import DirectoryDependency from .file_dependency import FileDependency from .url_dependency import URLDependency from .vcs_dependency import VCSDependency if self.source_type == "directory": dep = DirectoryDependency( self._name, Path(self._source_url), category=self.category, optional=self.optional, base=self.root_dir, develop=self.develop, extras=self.features, ) elif self.source_type == "file": dep = FileDependency( self._name, Path(self._source_url), category=self.category, optional=self.optional, base=self.root_dir, extras=self.features, ) elif self.source_type == "url": dep = URLDependency( self._name, self._source_url, category=self.category, optional=self.optional, extras=self.features, ) elif self.source_type == "git": dep = VCSDependency( self._name, self.source_type, self.source_url, rev=self.source_reference, resolved_rev=self.source_resolved_reference, category=self.category, optional=self.optional, develop=self.develop, extras=self.features, ) else: dep = Dependency(self._name, self._version, extras=self.features) if not self.marker.is_any(): dep.marker = self.marker if not self.python_constraint.is_any(): dep.python_versions = self.python_versions if self._source_type not in ["directory", "file", "url", "git"]: return dep return dep.with_constraint(self._version) @contextmanager def with_python_versions(self, python_versions: str) -> None: original_python_versions = self.python_versions self.python_versions = python_versions yield self.python_versions = original_python_versions def with_features(self, features: List[str]) -> "Package": package = self.clone() package._features = frozenset(features) return package def without_features(self) -> "Package": return self.with_features([]) def clone(self) -> "Package": if self.is_root(): clone = self.__class__(self.pretty_name, self.version) else: clone = self.__class__( self.pretty_name, self.version, source_type=self._source_type, source_url=self._source_url, source_reference=self._source_reference, features=list(self.features), ) clone.description = self.description clone.category = self.category clone.optional = self.optional clone.python_versions = self.python_versions clone.marker = self.marker clone.extras = self.extras clone.root_dir = self.root_dir clone.develop = self.develop for dep in self.requires: clone.requires.append(dep) for dep in self.dev_requires: clone.dev_requires.append(dep) return clone def __hash__(self) -> int: return super(Package, self).__hash__() ^ hash(self._version) def __eq__(self, other: "Package") -> bool: if not isinstance(other, Package): return NotImplemented return self.is_same_package_as( other) and self._version == other.version def __str__(self) -> str: return "{} ({})".format(self.complete_name, self.full_pretty_version) def __repr__(self) -> str: args = [repr(self._name), repr(self._version.text)] if self._features: args.append("features={}".format(repr(self._features))) if self._source_type: args.append("source_type={}".format(repr(self._source_type))) args.append("source_url={}".format(repr(self._source_url))) if self._source_reference: args.append("source_reference={}".format( repr(self._source_reference))) if self._source_resolved_reference: args.append("source_resolved_reference={}".format( repr(self._source_resolved_reference))) return "Package({})".format(", ".join(args))
class Dependency(PackageSpecification): def __init__( self, name: str, constraint: Union[str, "VersionTypes"], optional: bool = False, category: str = "main", allows_prereleases: bool = False, extras: Union[List[str], FrozenSet[str]] = None, source_type: Optional[str] = None, source_url: Optional[str] = None, source_reference: Optional[str] = None, source_resolved_reference: Optional[str] = None, ): from poetry.core.semver.version_range import VersionRange from poetry.core.version.markers import AnyMarker super(Dependency, self).__init__( name, source_type=source_type, source_url=source_url, source_reference=source_reference, source_resolved_reference=source_resolved_reference, features=extras, ) self._constraint = None self.set_constraint(constraint=constraint) self._pretty_constraint = str(constraint) self._optional = optional self._category = category if isinstance(self._constraint, VersionRange) and self._constraint.min: allows_prereleases = ( allows_prereleases or self._constraint.min.is_unstable() ) self._allows_prereleases = allows_prereleases self._python_versions = "*" self._python_constraint = parse_constraint("*") self._transitive_python_versions = None self._transitive_python_constraint = None self._transitive_marker = None self._extras = frozenset(extras or []) self._in_extras = [] self._activated = not self._optional self.is_root = False self.marker = AnyMarker() self.source_name = None @property def name(self) -> str: return self._name @property def constraint(self) -> "VersionTypes": return self._constraint def set_constraint(self, constraint: Union[str, "VersionTypes"]) -> None: from poetry.core.semver.version_constraint import VersionConstraint try: if not isinstance(constraint, VersionConstraint): self._constraint = parse_constraint(constraint) else: self._constraint = constraint except ValueError: self._constraint = parse_constraint("*") @property def pretty_constraint(self) -> str: return self._pretty_constraint @property def pretty_name(self) -> str: return self._pretty_name @property def category(self) -> str: return self._category @property def python_versions(self) -> str: return self._python_versions @python_versions.setter def python_versions(self, value: str) -> None: self._python_versions = value self._python_constraint = parse_constraint(value) if not self._python_constraint.is_any(): self.marker = self.marker.intersect( parse_marker( self._create_nested_marker( "python_version", self._python_constraint ) ) ) @property def transitive_python_versions(self) -> str: if self._transitive_python_versions is None: return self._python_versions return self._transitive_python_versions @transitive_python_versions.setter def transitive_python_versions(self, value: str) -> None: self._transitive_python_versions = value self._transitive_python_constraint = parse_constraint(value) @property def transitive_marker(self) -> "BaseMarker": if self._transitive_marker is None: return self.marker return self._transitive_marker @transitive_marker.setter def transitive_marker(self, value: "BaseMarker") -> None: self._transitive_marker = value @property def python_constraint(self) -> "VersionTypes": return self._python_constraint @property def transitive_python_constraint(self) -> "VersionTypes": if self._transitive_python_constraint is None: return self._python_constraint return self._transitive_python_constraint @property def extras(self) -> FrozenSet[str]: return self._extras @property def in_extras(self) -> List[str]: return self._in_extras @property def base_pep_508_name(self) -> str: from poetry.core.semver.version import Version from poetry.core.semver.version_union import VersionUnion requirement = self.pretty_name if self.extras: requirement += "[{}]".format(",".join(self.extras)) if isinstance(self.constraint, VersionUnion): if self.constraint.excludes_single_version(): requirement += " ({})".format(str(self.constraint)) else: constraints = self.pretty_constraint.split(",") constraints = [parse_constraint(c) for c in constraints] constraints = [str(c) for c in constraints] requirement += " ({})".format(",".join(constraints)) elif isinstance(self.constraint, Version): requirement += " (=={})".format(self.constraint.text) elif not self.constraint.is_any(): requirement += " ({})".format(str(self.constraint).replace(" ", "")) return requirement def allows_prereleases(self) -> bool: return self._allows_prereleases def is_optional(self) -> bool: return self._optional def is_activated(self) -> bool: return self._activated def is_vcs(self) -> bool: return False def is_file(self) -> bool: return False def is_directory(self) -> bool: return False def is_url(self) -> bool: return False def accepts(self, package: "Package") -> bool: """ Determines if the given package matches this dependency. """ return ( self._name == package.name and self._constraint.allows(package.version) and (not package.is_prerelease() or self.allows_prereleases()) ) def to_pep_508(self, with_extras: bool = True) -> str: from .utils.utils import convert_markers requirement = self.base_pep_508_name markers = [] has_extras = False if not self.marker.is_any(): marker = self.marker if not with_extras: marker = marker.without_extras() # we re-check for any marker here since the without extra marker might # return an any marker again if not marker.is_empty() and not marker.is_any(): markers.append(str(marker)) has_extras = "extra" in convert_markers(marker) else: # Python marker if self.python_versions != "*": python_constraint = self.python_constraint markers.append( self._create_nested_marker("python_version", python_constraint) ) in_extras = " || ".join(self._in_extras) if in_extras and with_extras and not has_extras: markers.append( self._create_nested_marker("extra", parse_generic_constraint(in_extras)) ) if markers: if self.is_vcs() or self.is_url() or self.is_file(): requirement += " " if len(markers) > 1: markers = ["({})".format(m) for m in markers] requirement += "; {}".format(" and ".join(markers)) else: requirement += "; {}".format(markers[0]) return requirement def _create_nested_marker( self, name: str, constraint: Union["BaseConstraint", "VersionTypes"] ) -> str: from poetry.core.semver.version import Version from poetry.core.semver.version_union import VersionUnion from .constraints.constraint import Constraint from .constraints.multi_constraint import MultiConstraint from .constraints.union_constraint import UnionConstraint if isinstance(constraint, (MultiConstraint, UnionConstraint)): parts = [] for c in constraint.constraints: multi = False if isinstance(c, (MultiConstraint, UnionConstraint)): multi = True parts.append((multi, self._create_nested_marker(name, c))) glue = " and " if isinstance(constraint, UnionConstraint): parts = [ "({})".format(part[1]) if part[0] else part[1] for part in parts ] glue = " or " else: parts = [part[1] for part in parts] marker = glue.join(parts) elif isinstance(constraint, Constraint): marker = '{} {} "{}"'.format(name, constraint.operator, constraint.version) elif isinstance(constraint, VersionUnion): parts = [] for c in constraint.ranges: parts.append(self._create_nested_marker(name, c)) glue = " or " parts = ["({})".format(part) for part in parts] marker = glue.join(parts) elif isinstance(constraint, Version): if constraint.precision >= 3 and name == "python_version": name = "python_full_version" marker = '{} == "{}"'.format(name, constraint.text) else: if constraint.min is not None: min_name = name if constraint.min.precision >= 3 and name == "python_version": min_name = "python_full_version" if constraint.max is None: name = min_name op = ">=" if not constraint.include_min: op = ">" version = constraint.min.text if constraint.max is not None: max_name = name if constraint.max.precision >= 3 and name == "python_version": max_name = "python_full_version" text = '{} {} "{}"'.format(min_name, op, version) op = "<=" if not constraint.include_max: op = "<" version = constraint.max text += ' and {} {} "{}"'.format(max_name, op, version) return text elif constraint.max is not None: if constraint.max.precision >= 3 and name == "python_version": name = "python_full_version" op = "<=" if not constraint.include_max: op = "<" version = constraint.max else: return "" marker = '{} {} "{}"'.format(name, op, version) return marker def activate(self) -> None: """ Set the dependency as mandatory. """ self._activated = True def deactivate(self) -> None: """ Set the dependency as optional. """ if not self._optional: self._optional = True self._activated = False def with_constraint(self, constraint: Union[str, "VersionTypes"]) -> "Dependency": new = Dependency( self.pretty_name, constraint, optional=self.is_optional(), category=self.category, allows_prereleases=self.allows_prereleases(), extras=self._extras, source_type=self._source_type, source_url=self._source_url, source_reference=self._source_reference, ) new.is_root = self.is_root new.python_versions = self.python_versions new.transitive_python_versions = self.transitive_python_versions new.marker = self.marker new.transitive_marker = self.transitive_marker for in_extra in self.in_extras: new.in_extras.append(in_extra) return new @classmethod def create_from_pep_508( cls, name: str, relative_to: Optional[Path] = None ) -> "DependencyTypes": """ Resolve a PEP-508 requirement string to a `Dependency` instance. If a `relative_to` path is specified, this is used as the base directory if the identified dependency is of file or directory type. """ from poetry.core.utils.patterns import wheel_file_re from poetry.core.vcs.git import ParsedUrl from poetry.core.version.requirements import Requirement from .url_dependency import URLDependency from .utils.link import Link from .utils.utils import convert_markers from .utils.utils import is_archive_file from .utils.utils import is_installable_dir from .utils.utils import is_url from .utils.utils import path_to_url from .utils.utils import strip_extras from .utils.utils import url_to_path from .vcs_dependency import VCSDependency # Removing comments parts = name.split("#", 1) name = parts[0].strip() if len(parts) > 1: rest = parts[1] if " ;" in rest: name += " ;" + rest.split(" ;", 1)[1] req = Requirement(name) if req.marker: markers = convert_markers(req.marker) else: markers = {} name = req.name path = os.path.normpath(os.path.abspath(name)) link = None if is_url(name): link = Link(name) elif req.url: link = Link(req.url) else: p, extras = strip_extras(path) if os.path.isdir(p) and (os.path.sep in name or name.startswith(".")): if not is_installable_dir(p): raise ValueError( "Directory {!r} is not installable. File 'setup.py' " "not found.".format(name) ) link = Link(path_to_url(p)) elif is_archive_file(p): link = Link(path_to_url(p)) # it's a local file, dir, or url if link: is_file_uri = link.scheme == "file" is_relative_uri = is_file_uri and re.search(r"\.\./", link.url) # Handle relative file URLs if is_file_uri and is_relative_uri: path = Path(link.path) if relative_to: path = relative_to / path link = Link(path_to_url(path)) # wheel file version = None if link.is_wheel: m = wheel_file_re.match(link.filename) if not m: raise ValueError("Invalid wheel name: {}".format(link.filename)) name = m.group("name") version = m.group("ver") name = req.name or link.egg_fragment dep = None if link.scheme.startswith("git+"): url = ParsedUrl.parse(link.url) dep = VCSDependency( name, "git", url.url, rev=url.rev, extras=req.extras ) elif link.scheme == "git": dep = VCSDependency( name, "git", link.url_without_fragment, extras=req.extras ) elif link.scheme in ["http", "https"]: dep = URLDependency(name, link.url) elif is_file_uri: # handle RFC 8089 references path = url_to_path(req.url) dep = _make_file_or_dir_dep( name=name, path=path, base=relative_to, extras=req.extras ) else: try: # this is a local path not using the file URI scheme dep = _make_file_or_dir_dep( name=name, path=Path(req.url), base=relative_to, extras=req.extras, ) except ValueError: pass if dep is None: dep = Dependency(name, version or "*", extras=req.extras) if version: dep._constraint = parse_constraint(version) else: if req.pretty_constraint: constraint = req.constraint else: constraint = "*" dep = Dependency(name, constraint, extras=req.extras) if "extra" in markers: # If we have extras, the dependency is optional dep.deactivate() for or_ in markers["extra"]: for _, extra in or_: dep.in_extras.append(extra) if "python_version" in markers: ors = [] for or_ in markers["python_version"]: ands = [] for op, version in or_: # Expand python version if op == "==" and "*" not in version: version = "~" + version op = "" elif op == "!=": version += ".*" elif op in ("in", "not in"): versions = [] for v in re.split("[ ,]+", version): split = v.split(".") if len(split) in [1, 2]: split.append("*") op_ = "" if op == "in" else "!=" else: op_ = "==" if op == "in" else "!=" versions.append(op_ + ".".join(split)) glue = " || " if op == "in" else ", " if versions: ands.append(glue.join(versions)) continue ands.append("{}{}".format(op, version)) ors.append(" ".join(ands)) dep.python_versions = " || ".join(ors) if req.marker: dep.marker = req.marker return dep def __eq__(self, other: Any) -> bool: if not isinstance(other, Dependency): return NotImplemented return ( self.is_same_package_as(other) and self._constraint == other.constraint and self._extras == other.extras ) def __ne__(self, other: Any) -> bool: return not self == other def __hash__(self) -> int: return ( super(Dependency, self).__hash__() ^ hash(self._constraint) ^ hash(self._extras) ) def __str__(self) -> str: if self.is_root: return self._pretty_name return self.base_pep_508_name def __repr__(self) -> str: return "<{} {}>".format(self.__class__.__name__, str(self))