def __init__( self, name: str, constraint: Union[str, "VersionTypes"], optional: bool = False, groups: Optional[List[str]] = None, 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, source_subdirectory: Optional[str] = None, ): 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, source_subdirectory=source_subdirectory, features=extras, ) self._constraint = None self.set_constraint(constraint=constraint) self._pretty_constraint = str(constraint) self._optional = optional if not groups: groups = ["default"] self._groups = frozenset(groups) if (isinstance(self._constraint, VersionRangeConstraint) 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
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
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_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
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
def __init__( self, name: str, constraint: str | VersionConstraint, optional: bool = False, groups: Iterable[str] | None = None, allows_prereleases: bool = False, extras: Iterable[str] | None = None, source_type: str | None = None, source_url: str | None = None, source_reference: str | None = None, source_resolved_reference: str | None = None, source_subdirectory: str | None = None, ) -> None: from poetry.core.version.markers import AnyMarker super().__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=extras, ) self._constraint: VersionConstraint self._pretty_constraint: str self.set_constraint(constraint=constraint) self._optional = optional if not groups: groups = [MAIN_GROUP] self._groups = frozenset(groups) if (isinstance(self._constraint, VersionRangeConstraint) 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: str | None = None self._transitive_python_constraint: VersionConstraint | None = None self._transitive_marker: BaseMarker | None = None self._extras = frozenset(extras or []) self._in_extras: list[str] = [] self._activated = not self._optional self.is_root = False self._marker: BaseMarker = AnyMarker() self.source_name = None
def __init__( self, name, # type: str constraint, # type: str optional=False, # type: bool category="main", # type: str allows_prereleases=False, # type: bool source_name=None, # type: Optional[str] ): self._name = canonicalize_name(name) self._pretty_name = name 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._source_name = source_name self._python_versions = "*" self._python_constraint = parse_constraint("*") self._transitive_python_versions = None self._transitive_python_constraint = None self._transitive_marker = None self._extras = [] self._in_extras = [] self._activated = not self._optional self.is_root = False self.marker = AnyMarker()
def __init__( self, name: str, version: str | Version, pretty_version: str | None = None, source_type: str | None = None, source_url: str | None = None, source_reference: str | None = None, source_resolved_reference: str | None = None, source_subdirectory: str | None = None, features: Iterable[str] | None = 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().__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: list[str] = [] self._maintainers: list[str] = [] self.homepage: str | None = None self.repository_url: str | None = None self.documentation_url: str | None = None self.keywords: list[str] = [] self._license: License | None = None self.readmes: tuple[Path, ...] = () self.extras: dict[str, list[Dependency]] = {} self.requires_extras: list[str] = [] self._dependency_groups: dict[str, DependencyGroup] = {} # For compatibility with previous version, we keep the category self.category = "main" self.files: list[dict[str, str]] = [] self.optional = False self.classifiers: list[str] = [] self._python_versions = "*" self._python_constraint = parse_constraint("*") self._python_marker: BaseMarker = AnyMarker() self.platform = None self.marker: BaseMarker = AnyMarker() self.root_dir: Path | None = None self.develop = develop
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))
def create_dependency( cls, name: str, constraint: DependencyConstraint, groups: list[str] | None = None, root_dir: Path | None = None, ) -> Dependency: from poetry.core.packages.constraints import ( parse_constraint as parse_generic_constraint, ) from poetry.core.packages.dependency import Dependency from poetry.core.packages.dependency_group import MAIN_GROUP from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.url_dependency import URLDependency from poetry.core.packages.utils.utils import create_nested_marker from poetry.core.packages.vcs_dependency import VCSDependency from poetry.core.semver.helpers import parse_constraint from poetry.core.version.markers import AnyMarker from poetry.core.version.markers import parse_marker if groups is None: groups = [MAIN_GROUP] 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 = ( f'The "{name}" dependency specifies ' 'the "allows-prereleases" property, which is deprecated. ' 'Use "allow-prereleases" instead.') warn(message, DeprecationWarning) logger.warning(message) allows_prereleases = constraint.get( "allow-prereleases", constraint.get("allows-prereleases", False)) dependency: Dependency 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), directory=constraint.get("subdirectory", None), groups=groups, optional=optional, develop=constraint.get("develop", False), extras=constraint.get("extras", []), ) elif "file" in constraint: file_path = Path(constraint["file"]) dependency = FileDependency( name, file_path, groups=groups, base=root_dir, extras=constraint.get("extras", []), ) elif "path" in constraint: path = Path(constraint["path"]) if root_dir: is_file = root_dir.joinpath(path).is_file() else: is_file = path.is_file() if is_file: dependency = FileDependency( name, path, groups=groups, optional=optional, base=root_dir, extras=constraint.get("extras", []), ) else: dependency = DirectoryDependency( name, path, groups=groups, optional=optional, base=root_dir, develop=constraint.get("develop", False), extras=constraint.get("extras", []), ) elif "url" in constraint: dependency = URLDependency( name, constraint["url"], groups=groups, optional=optional, extras=constraint.get("extras", []), ) else: version = constraint["version"] dependency = Dependency( name, version, optional=optional, groups=groups, allows_prereleases=allows_prereleases, extras=constraint.get("extras", []), ) marker = parse_marker(markers) if markers else AnyMarker() if python_versions: marker = marker.intersect( parse_marker( create_nested_marker( "python_version", parse_constraint(python_versions)))) if platform: marker = marker.intersect( parse_marker( create_nested_marker( "sys_platform", parse_generic_constraint(platform)))) if not marker.is_any(): dependency.marker = marker dependency.source_name = constraint.get("source") else: dependency = Dependency(name, constraint, groups=groups) return dependency
def complete_package(self, package: DependencyPackage) -> DependencyPackage: if package.is_root(): package = package.clone() requires = package.all_requires elif not package.is_root() and package.source_type not in { "directory", "file", "url", "git", }: package = DependencyPackage( package.dependency, self._pool.package( package.name, package.version.text, extras=list(package.dependency.extras), repository=package.dependency.source_name, ), ) requires = package.requires else: requires = package.requires if self._load_deferred: # Retrieving constraints for deferred dependencies for r in requires: if r.is_directory(): self.search_for_directory(r) elif r.is_file(): self.search_for_file(r) elif r.is_vcs(): self.search_for_vcs(r) elif r.is_url(): self.search_for_url(r) optional_dependencies = [] _dependencies = [] # If some extras/features were required, we need to # add a special dependency representing the base package # to the current package if package.dependency.extras: for extra in package.dependency.extras: if extra not in package.extras: continue optional_dependencies += [ d.name for d in package.extras[extra] ] package = package.with_features(list(package.dependency.extras)) _dependencies.append(package.without_features().to_dependency()) for dep in requires: if not self._python_constraint.allows_any(dep.python_constraint): continue if dep.name in self.UNSAFE_PACKAGES: continue if self._env and not dep.marker.validate(self._env.marker_env): continue if not package.is_root() and ( (dep.is_optional() and dep.name not in optional_dependencies) or (dep.in_extras and not set(dep.in_extras).intersection( package.dependency.extras))): continue _dependencies.append(dep) dependencies = self._get_dependencies_with_overrides( _dependencies, package) # Searching for duplicate dependencies # # If the duplicate dependencies have the same constraint, # the requirements will be merged. # # For instance: # - enum34; python_version=="2.7" # - enum34; python_version=="3.3" # # will become: # - enum34; python_version=="2.7" or python_version=="3.3" # # If the duplicate dependencies have different constraints # we have to split the dependency graph. # # An example of this is: # - pypiwin32 (220); sys_platform == "win32" and python_version >= "3.6" # - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6" duplicates: dict[str, list[Dependency]] = {} for dep in dependencies: if dep.complete_name not in duplicates: duplicates[dep.complete_name] = [] duplicates[dep.complete_name].append(dep) dependencies = [] for dep_name, deps in duplicates.items(): if len(deps) == 1: dependencies.append(deps[0]) continue self.debug(f"<debug>Duplicate dependencies for {dep_name}</debug>") deps = self._merge_dependencies_by_marker(deps) deps = self._merge_dependencies_by_constraint(deps) if len(deps) == 1: self.debug( f"<debug>Merging requirements for {deps[0]!s}</debug>") dependencies.append(deps[0]) continue # We leave dependencies as-is if they have the same # python/platform constraints. # That way the resolver will pickup the conflict # and display a proper error. seen = set() for dep in deps: pep_508_dep = dep.to_pep_508(False) if ";" not in pep_508_dep: _requirements = "" else: _requirements = pep_508_dep.split(";")[1].strip() if _requirements not in seen: seen.add(_requirements) if len(deps) != len(seen): for dep in deps: dependencies.append(dep) continue # At this point, we raise an exception that will # tell the solver to make new resolutions with specific overrides. # # For instance, if the foo (1.2.3) package has the following dependencies: # - bar (>=2.0) ; python_version >= "3.6" # - bar (<2.0) ; python_version < "3.6" # # then the solver will need to make two new resolutions # with the following overrides: # - {<Package foo (1.2.3): {"bar": <Dependency bar (>=2.0)>} # - {<Package foo (1.2.3): {"bar": <Dependency bar (<2.0)>} def fmt_warning(d: Dependency) -> str: marker = d.marker if not d.marker.is_any() else "*" return ( f"<c1>{d.name}</c1> <fg=default>(<c2>{d.pretty_constraint}</c2>)</>" f" with markers <b>{marker}</b>") warnings = ", ".join(fmt_warning(d) for d in deps[:-1]) warnings += f" and {fmt_warning(deps[-1])}" self.debug( f"<warning>Different requirements found for {warnings}.</warning>" ) # We need to check if one of the duplicate dependencies # has no markers. If there is one, we need to change its # environment markers to the inverse of the union of the # other dependencies markers. # For instance, if we have the following dependencies: # - ipython # - ipython (1.2.4) ; implementation_name == "pypy" # # the marker for `ipython` will become `implementation_name != "pypy"`. # # Further, we have to merge the constraints of the requirements # without markers into the constraints of the requirements with markers. # for instance, if we have the following dependencies: # - foo (>= 1.2) # - foo (!= 1.2.1) ; python == 3.10 # # the constraint for the second entry will become (!= 1.2.1, >= 1.2) any_markers_dependencies = [d for d in deps if d.marker.is_any()] other_markers_dependencies = [ d for d in deps if not d.marker.is_any() ] marker = other_markers_dependencies[0].marker for other_dep in other_markers_dependencies[1:]: marker = marker.union(other_dep.marker) inverted_marker = marker.invert() if any_markers_dependencies: for dep_any in any_markers_dependencies: dep_any.marker = inverted_marker for dep_other in other_markers_dependencies: dep_other.set_constraint( dep_other.constraint.intersect(dep_any.constraint)) elif not inverted_marker.is_empty( ) and self._python_constraint.allows_any( get_python_constraint_from_marker(inverted_marker)): # if there is no any marker dependency # and the inverted marker is not empty, # a dependency with the inverted union of all markers is required # in order to not miss other dependencies later, for instance: # - foo (1.0) ; python == 3.7 # - foo (2.0) ; python == 3.8 # - bar (2.0) ; python == 3.8 # - bar (3.0) ; python == 3.9 # # the last dependency would be missed without this, # because the intersection with both foo dependencies is empty inverted_marker_dep = deps[0].with_constraint( EmptyConstraint()) inverted_marker_dep.marker = inverted_marker deps.append(inverted_marker_dep) overrides = [] overrides_marker_intersection: BaseMarker = AnyMarker() for dep_overrides in self._overrides.values(): for dep in dep_overrides.values(): overrides_marker_intersection = ( overrides_marker_intersection.intersect(dep.marker)) for dep in deps: if not overrides_marker_intersection.intersect( dep.marker).is_empty(): current_overrides = self._overrides.copy() package_overrides = current_overrides.get(package, {}).copy() package_overrides.update({dep.name: dep}) current_overrides.update({package: package_overrides}) overrides.append(current_overrides) if overrides: raise OverrideNeeded(*overrides) # Modifying dependencies as needed clean_dependencies = [] for dep in dependencies: if not package.dependency.transitive_marker.without_extras( ).is_any(): marker_intersection = (package.dependency.transitive_marker. without_extras().intersect( dep.marker.without_extras())) if marker_intersection.is_empty(): # The dependency is not needed, since the markers specified # for the current package selection are not compatible with # the markers for the current dependency, so we skip it continue dep.transitive_marker = marker_intersection if not package.dependency.python_constraint.is_any(): python_constraint_intersection = dep.python_constraint.intersect( package.dependency.python_constraint) if python_constraint_intersection.is_empty(): # This dependency is not needed under current python constraint. continue dep.transitive_python_versions = str( python_constraint_intersection) clean_dependencies.append(dep) package = DependencyPackage( package.dependency, package.with_dependency_groups([], only=True)) for dep in clean_dependencies: package.add_dependency(dep) return package
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))
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, 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, 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 = develop
'python_version >= "3.6" or python_version < "3.7"', ), ], ) def test_union_should_drop_markers_if_their_complement_is_present( marker: str, expected: str) -> None: m = parse_marker(marker) assert parse_marker(expected) == m @pytest.mark.parametrize( "scheme, marker, expected", [ ("empty", EmptyMarker(), EmptyMarker()), ("any", AnyMarker(), AnyMarker()), ( "A_", SingleMarker("python_version", ">=3.7"), SingleMarker("python_version", ">=3.7"), ), ( "AB_", MultiMarker( SingleMarker("python_version", ">=3.7"), SingleMarker("python_version", "<3.9"), ), MultiMarker( SingleMarker("python_version", ">=3.7"), SingleMarker("python_version", "<3.9"), ),