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)
def write(self) -> str: buffer = [] required_python_version_notification = False for incompatibility in self._root.external_incompatibilities: if isinstance(incompatibility.cause, PythonCause): if not required_python_version_notification: buffer.append( "The current project's Python requirement ({}) " "is not compatible with some of the required " "packages Python requirement:".format( incompatibility.cause.root_python_version)) required_python_version_notification = True root_constraint = parse_constraint( incompatibility.cause.root_python_version) constraint = parse_constraint( incompatibility.cause.python_version) buffer.append( " - {} requires Python {}, so it will not be satisfied for Python {}" .format( incompatibility.terms[0].dependency.name, incompatibility.cause.python_version, root_constraint.difference(constraint), )) if required_python_version_notification: buffer.append("") if isinstance(self._root.cause, ConflictCause): self._visit(self._root, {}) else: self._write( self._root, "Because {}, version solving failed.".format(self._root)) padding = (0 if not self._line_numbers else len("({}) ".format( list(self._line_numbers.values())[-1]))) last_was_empty = False for line in self._lines: message = line[0] if not message: if not last_was_empty: buffer.append("") last_was_empty = True continue last_was_empty = False number = line[-1] if number is not None: message = "({})".format(number).ljust(padding) + message else: message = " " * padding + message buffer.append(message) return "\n".join(buffer)
def __init__(self, exception: "PackageNotFoundCause") -> None: from poetry.core.semver.helpers import parse_constraint from poetry.mixology.incompatibility_cause import PythonCause self._title = "Check your dependencies Python requirement." failure = exception.error version_solutions = [] for incompatibility in failure._incompatibility.external_incompatibilities: if isinstance(incompatibility.cause, PythonCause): root_constraint = parse_constraint( incompatibility.cause.root_python_version ) constraint = parse_constraint(incompatibility.cause.python_version) version_solutions.append( "For <fg=default;options=bold>{}</>, a possible solution would be " 'to set the `<fg=default;options=bold>python</>` property to <fg=yellow>"{}"</>'.format( incompatibility.terms[0].dependency.name, root_constraint.intersect(constraint), ) ) description = ( "The Python requirement can be specified via the `<fg=default;options=bold>python</>` " "or `<fg=default;options=bold>markers</>` properties" ) if version_solutions: description += "\n\n" + "\n".join(version_solutions) description += "\n" self._description = description
def handle(self) -> None: from pathlib import Path from poetry.core.semver.helpers import parse_constraint from poetry.core.vcs.git import GitConfig from poetry.layouts import layout from poetry.utils.env import SystemEnv if self.option("src"): layout_ = layout("src") else: layout_ = layout("standard") path = Path.cwd() / Path(self.argument("path")) name = self.option("name") if not name: name = path.name if path.exists(): if list(path.glob("*")): # Directory is not empty. Aborting. raise RuntimeError("Destination <fg=yellow>{}</> " "exists and is not empty".format(path)) readme_format = "rst" config = GitConfig() author = None if config.get("user.name"): author = config["user.name"] author_email = config.get("user.email") if author_email: author += " <{}>".format(author_email) current_env = SystemEnv(Path(sys.executable)) default_python = "^{}".format(".".join( str(v) for v in current_env.version_info[:2])) dev_dependencies = {} python_constraint = parse_constraint(default_python) if parse_constraint("<3.5").allows_any(python_constraint): dev_dependencies["pytest"] = "^4.6" if parse_constraint(">=3.5").allows_all(python_constraint): dev_dependencies["pytest"] = "^5.2" layout_ = layout_( name, "0.1.0", author=author, readme_format=readme_format, python=default_python, dev_dependencies=dev_dependencies, ) layout_.create(path) self.line("Created package <info>{}</> in <fg=blue>{}</>".format( module_name(name), path.relative_to(Path.cwd())))
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("*")
def set_constraint(self, constraint: str | VersionConstraint) -> 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("*") self._pretty_constraint = str(constraint)
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
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: extras = ",".join(sorted(self.extras)) requirement += f"[{extras}]" constraint = self.constraint if isinstance(constraint, VersionUnion): if (constraint.excludes_single_version() or constraint.excludes_single_wildcard_range()): # This branch is a short-circuit logic for special cases and # avoids having to split and parse constraint again. This has # no functional difference with the logic in the else branch. requirement += f" ({str(constraint)})" else: constraints = ",".join( str(parse_constraint(c)) for c in self.pretty_constraint.split(",")) requirement += f" ({constraints})" elif isinstance(constraint, Version): requirement += f" (=={constraint.text})" elif not constraint.is_any(): requirement += f" ({str(constraint).replace(' ', '')})" return requirement
def _get_lock_data(self) -> "TOMLDocument": if not self._lock.exists(): raise RuntimeError( "No lockfile found. Unable to read locked packages") try: lock_data = self._lock.read() except TOMLKitError as e: raise RuntimeError(f"Unable to read the lock file ({e}).") lock_version = Version.parse(lock_data["metadata"].get( "lock-version", "1.0")) current_version = Version.parse(self._VERSION) # We expect the locker to be able to read lock files # from the same semantic versioning range accepted_versions = parse_constraint("^{}".format( Version.from_parts(current_version.major, 0))) lock_version_allowed = accepted_versions.allows(lock_version) if lock_version_allowed and current_version < lock_version: logger.warning( "The lock file might not be compatible with the current version of Poetry.\n" "Upgrade Poetry to ensure the lock file is read properly or, alternatively, " "regenerate the lock file with the `poetry lock` command.") elif not lock_version_allowed: raise RuntimeError( "The lock file is not compatible with the current version of Poetry.\n" "Upgrade Poetry to be able to read the lock file or, alternatively, " "regenerate the lock file with the `poetry lock` command.") return lock_data
def test_parse_constraint_multi(input: str) -> None: assert parse_constraint(input) == VersionRange( Version.from_parts(2, 0, 0), Version.from_parts(3, 0, 0), include_min=False, include_max=True, )
def test_parse_constraint_multi_wilcard(input): assert parse_constraint(input) == VersionUnion( VersionRange( Version.from_parts(2, 7, 0), Version.from_parts(3, 0, 0), True, False ), VersionRange(Version.from_parts(3, 2, 0), None, True, False), )
def get_release_info(self, name: str, version: str) -> "PackageInfo": """ Return the release information given a package name and a version. The information is returned from the cache if it exists or retrieved from the remote server. """ from poetry.inspection.info import PackageInfo if self._disable_cache: return PackageInfo.load(self._get_release_info(name, version)) cached = self._cache.remember_forever( f"{name}:{version}", lambda: self._get_release_info(name, version)) cache_version = cached.get("_cache_version", "0.0.0") if parse_constraint(cache_version) != self.CACHE_VERSION: # The cache must be updated self._log( f"The cache for {name} {version} is outdated. Refreshing.", level="debug", ) cached = self._get_release_info(name, version) self._cache.forever(f"{name}:{version}", cached) return PackageInfo.load(cached)
def marker(self, marker: str | BaseMarker) -> None: from poetry.core.packages.utils.utils import convert_markers from poetry.core.semver.helpers import parse_constraint from poetry.core.version.markers import BaseMarker from poetry.core.version.markers import parse_marker if not isinstance(marker, BaseMarker): marker = parse_marker(marker) self._marker = marker markers = convert_markers(marker) if "extra" in markers: # If we have extras, the dependency is optional self.deactivate() for or_ in markers["extra"]: for _, extra in or_: self.in_extras.append(extra) # Recalculate python versions. self._python_versions = "*" if not contains_group_without_marker(markers, "python_version"): 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(f"{op}{version}") ors.append(" ".join(ands)) self._python_versions = " || ".join(ors) self._python_constraint = parse_constraint(self._python_versions)
def test_create_dependency_marker_variants(constraint: dict[str, Any], exp_python: str, exp_marker: str) -> None: constraint["version"] = "1.0.0" dep = Factory.create_dependency("foo", constraint) assert dep.python_versions == exp_python assert dep.python_constraint == parse_constraint(exp_python) assert str(dep.marker) == exp_marker
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)))
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: 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 python_versions(self, value: str) -> None: self._python_versions = value if value == "*": value = "~2.7 || >=3.4" self._python_constraint = parse_constraint(value) self._python_marker = parse_marker( create_nested_marker("python_version", self._python_constraint))
def __init__(self, requirement_string: str) -> None: from lark import UnexpectedCharacters from lark import UnexpectedToken try: parsed = _parser.parse(requirement_string) except (UnexpectedCharacters, UnexpectedToken) as e: raise InvalidRequirement( "The requirement is invalid: Unexpected character at column {}\n\n{}" .format(e.column, e.get_context(requirement_string))) self.name = next(parsed.scan_values(lambda t: t.type == "NAME")).value url = next(parsed.scan_values(lambda t: t.type == "URI"), None) if url: url = url.value parsed_url = urlparse.urlparse(url) if parsed_url.scheme == "file": if urlparse.urlunparse(parsed_url) != url: raise InvalidRequirement( 'The requirement is invalid: invalid URL "{0}"'.format( url)) elif (not (parsed_url.scheme and parsed_url.netloc) or (not parsed_url.scheme and not parsed_url.netloc)) and not parsed_url.path: raise InvalidRequirement( 'The requirement is invalid: invalid URL "{0}"'.format( url)) self.url = url else: self.url = None self.extras = [ e.value for e in parsed.scan_values(lambda t: t.type == "EXTRA") ] constraint = next(parsed.find_data("version_specification"), None) if not constraint: constraint = "*" else: constraint = ",".join(constraint.children) try: self.constraint = parse_constraint(constraint) except ParseConstraintError: raise InvalidRequirement( 'The requirement is invalid: invalid version constraint "{}"'. format(constraint)) self.pretty_constraint = constraint marker = next(parsed.find_data("marker_spec"), None) if marker: marker = _compact_markers(marker.children[0].children, tree_prefix="markers__") self.marker = marker
def python_versions(self, value: Union[str, "VersionTypes"]) -> None: from poetry.core.semver.version_range import VersionRange self._python_versions = value if value == "*" or value == VersionRange(): value = "~2.7 || >=3.4" self._python_constraint = parse_constraint(value) self._python_marker = parse_marker( create_nested_marker("python_version", self._python_constraint))
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 format_python_constraint( constraint: Union[Version, VersionUnion, "VersionConstraint"], ) -> str: """ This helper will help in transforming disjunctive constraint into proper constraint. """ if isinstance(constraint, Version): if constraint.precision >= 3: return "=={}".format(str(constraint)) # Transform 3.6 or 3 if constraint.precision == 2: # 3.6 constraint = parse_constraint("~{}.{}".format( constraint.major, constraint.minor)) else: constraint = parse_constraint("^{}.0".format(constraint.major)) if not isinstance(constraint, VersionUnion): return str(constraint) formatted = [] accepted = [] for version in PYTHON_VERSION: version_constraint = parse_constraint(version) matches = constraint.allows_any(version_constraint) if not matches: formatted.append("!=" + version) else: accepted.append(version) # Checking lower bound low = accepted[0] formatted.insert(0, ">=" + ".".join(low.split(".")[:2])) return ", ".join(formatted)
def get_update_status(self, latest: "Package", package: "Package") -> str: from poetry.core.semver.helpers import parse_constraint if latest.full_pretty_version == package.full_pretty_version: return "up-to-date" constraint = parse_constraint("^" + package.pretty_version) if latest.version and constraint.allows(latest.version): # It needs an immediate semver-compliant upgrade return "semver-safe-update" # it needs an upgrade but has potential BC breaks so is not urgent return "update-possible"
def _get_constraints_from_dependency( dependency: Dependency, ) -> tuple[VersionConstraint, bool]: constraint = dependency.constraint if constraint is None: constraint = "*" if not isinstance(constraint, VersionConstraint): constraint = parse_constraint(constraint) allow_prereleases = dependency.allows_prereleases() if isinstance(constraint, VersionRange) and ( constraint.max is not None and constraint.max.is_unstable() or constraint.min is not None and constraint.min.is_unstable()): allow_prereleases = True return constraint, allow_prereleases
def __init__( self, name: str, version: str | Version, pretty_version: str | None = None, ) -> None: super().__init__(name, version, pretty_version) self.build_config: dict[str, Any] = {} self.packages: list[dict[str, Any]] = [] self.include: list[dict[str, Any]] = [] self.exclude: list[dict[str, Any]] = [] self.custom_urls: dict[str, str] = {} if self._python_versions == "*": self._python_constraint = parse_constraint("~2.7 || >=3.4")
def assert_requirement(req, name, url=None, extras=None, constraint="*", marker=None): if extras is None: extras = [] assert name == req.name assert url == req.url assert sorted(extras) == sorted(req.extras) assert parse_constraint(constraint) == req.constraint if marker: assert marker == str(req.marker)
def __init__( self, name: str, version: Union[str, "VersionTypes"], pretty_version: Optional[str] = None, ) -> None: super(ProjectPackage, self).__init__(name, version, pretty_version) self.build_config = dict() self.packages = [] self.include = [] self.exclude = [] self.custom_urls = {} if self._python_versions == "*": self._python_constraint = parse_constraint("~2.7 || >=3.4")
def find_packages(self, dependency: "Dependency") -> List["Package"]: from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version_constraint import VersionConstraint from poetry.core.semver.version_range import VersionRange constraint = dependency.constraint packages = [] ignored_pre_release_packages = [] if constraint is None: constraint = "*" if not isinstance(constraint, VersionConstraint): constraint = parse_constraint(constraint) allow_prereleases = dependency.allows_prereleases() if isinstance(constraint, VersionRange): if ( constraint.max is not None and constraint.max.is_unstable() or constraint.min is not None and constraint.min.is_unstable() ): allow_prereleases = True for package in self.packages: if dependency.name == package.name: if ( package.is_prerelease() and not allow_prereleases and not package.source_type ): # If prereleases are not allowed and the package is a prerelease # and is a standard package then we skip it if constraint.is_any(): # we need this when all versions of the package are pre-releases ignored_pre_release_packages.append(package) continue if constraint.allows(package.version) or ( package.is_prerelease() and constraint.allows(package.version.next_patch()) ): packages.append(package) return packages or ignored_pre_release_packages
def assert_requirement( req: Requirement, name: str, url: str | None = None, extras: list[str] | None = None, constraint: str = "*", marker: str | None = None, ) -> None: if extras is None: extras = [] assert name == req.name assert url == req.url assert sorted(extras) == sorted(req.extras) assert parse_constraint(constraint) == req.constraint if marker: assert marker == str(req.marker)
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