def get_requirements(items: dict, inculde_optional) -> Dict[str, Requirement]: rv = {} for k, v in items.items(): vc = None if isinstance(v, str): vc = parse_constraint(v) elif isinstance(v, dict): if not v.get('optional') or inculde_optional: version = v.get('version') if isinstance(version, str): vc = parse_constraint(version) else: raise NotImplementedError(type(v)) if vc: vcs = str(vc) if vcs == '*': rv[k] = Requirement(k) else: rv[k] = Requirement(k + vcs) return rv
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)
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): from poetry.core.semver 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 set_constraint(self, constraint): 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): # type: (Union[str, "VersionTypes"]) -> None try: if not isinstance(constraint, VersionConstraint): self._constraint = parse_constraint(constraint) else: self._constraint = constraint except ValueError: self._constraint = parse_constraint("*")
def handle(self): from pathlib import Path from poetry.core.semver 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 __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 find_packages(self, dependency: "Dependency") -> List["Package"]: 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_prerelease() or constraint.min is not None and constraint.min.is_prerelease()): 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 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. """ if self._disable_cache: return PackageInfo.load(self._get_release_info(name, version)) cached = self._cache.remember_forever( "{}:{}".format(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( "The cache for {} {} is outdated. Refreshing.".format( name, version), level="debug", ) cached = self._get_release_info(name, version) self._cache.forever("{}:{}".format(name, version), cached) return PackageInfo.load(cached)
def __init__(self, requirement_string): try: req = REQUIREMENT.parseString(requirement_string) except ParseException as e: raise InvalidRequirement( 'Invalid requirement, parse error at "{0!r}"'.format( requirement_string[e.loc:e.loc + 8])) self.name = req.name if req.url: parsed_url = urlparse.urlparse(req.url) if parsed_url.scheme == "file": if urlparse.urlunparse(parsed_url) != req.url: raise InvalidRequirement("Invalid URL given") 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("Invalid URL: {0}".format(req.url)) self.url = req.url else: self.url = None self.extras = set(req.extras.asList() if req.extras else []) constraint = req.specifier if not constraint: constraint = "*" self.constraint = parse_constraint(constraint) self.pretty_constraint = constraint self.marker = req.marker if req.marker else None
def get_python_constraint_from_marker( marker, ): # type: (BaseMarker) -> VersionConstraint python_marker = marker.only("python_version") if python_marker.is_any(): return VersionRange() if python_marker.is_empty(): return EmptyConstraint() markers = convert_markers(marker) ors = [] for or_ in markers["python_version"]: ands = [] for op, version in or_: # Expand python version if op == "==": version = "~" + version op = "" elif op == "!=": version += ".*" elif op in ("<=", ">"): parsed_version = Version.parse(version) if parsed_version.precision == 1: if op == "<=": op = "<" version = parsed_version.next_major.text elif op == ">": op = ">=" version = parsed_version.next_major.text elif parsed_version.precision == 2: if op == "<=": op = "<" version = parsed_version.next_minor.text elif op == ">": op = ">=" version = parsed_version.next_minor.text 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)) return parse_constraint(" || ".join(ors))
def _get_lock_data(self): # type: () -> dict 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("Unable to read the lock file ({}).".format(e)) lock_version = Version.parse(lock_data["metadata"].get( "lock-version", "1.0")) current_version = Version.parse(self._VERSION) accepted_versions = parse_constraint("^{}".format( Version(current_version.major, current_version.minor))) 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 find_packages(self, name, constraint=None, extras=None, allow_prereleases=False): packages = [] if constraint is None: constraint = "*" if not isinstance(constraint, VersionConstraint): constraint = parse_constraint(constraint) if isinstance(constraint, VersionRange): if (constraint.max is not None and constraint.max.is_prerelease() or constraint.min is not None and constraint.min.is_prerelease()): allow_prereleases = True key = name if not constraint.is_any(): key = "{}:{}".format(key, str(constraint)) if self._cache.store("matches").has(key): versions = self._cache.store("matches").get(key) else: page = self._get("/{}/".format( canonicalize_name(name).replace(".", "-"))) if page is None: return [] versions = [] for version in page.versions: if version.is_prerelease() and not allow_prereleases: continue if constraint.allows(version): versions.append(version) self._cache.store("matches").put(key, versions, 5) for version in versions: package = Package(name, version) package.source_type = "legacy" package.source_reference = self.name package.source_url = self._url if extras is not None: package.requires_extras = extras packages.append(package) self._log( "{} packages found for {} {}".format(len(packages), name, str(constraint)), level="debug", ) return packages
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)))
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, requirement_string): 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): 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 find_best_candidate( self, package_name, # type: str target_package_version=None, # type: Union[str, None] allow_prereleases=False, # type: bool source=None, # type: str ): # type: (...) -> Union[Package, bool] """ Given a package name and optional version, returns the latest Package that matches """ if target_package_version: constraint = parse_constraint(target_package_version) else: constraint = parse_constraint("*") candidates = self._pool.find_packages( package_name, constraint, allow_prereleases=True, repository=source ) only_prereleases = all([c.version.is_prerelease() for c in candidates]) if not candidates: return False dependency = Dependency(package_name, constraint) package = None for candidate in candidates: if ( candidate.is_prerelease() and not dependency.allows_prereleases() and not allow_prereleases and not only_prereleases ): continue # Select highest version of the two if package is None or package.version < candidate.version: package = candidate if package is None: return False return package
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, version, pretty_version=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, name, constraint=None, extras=None, allow_prereleases=False): name = name.lower() packages = [] ignored_pre_release_packages = [] if extras is None: extras = [] if constraint is None: constraint = "*" if not isinstance(constraint, VersionConstraint): constraint = parse_constraint(constraint) if isinstance(constraint, VersionRange): if (constraint.max is not None and constraint.max.is_prerelease() or constraint.min is not None and constraint.min.is_prerelease()): allow_prereleases = True for package in self.packages: if 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): for dep in package.requires: for extra in extras: if extra not in package.extras: continue reqs = package.extras[extra] for req in reqs: if req.name == dep.name: dep.activate() if extras: package.requires_extras = extras packages.append(package) return packages or ignored_pre_release_packages
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 format_python_constraint( constraint, ): # type: (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): from poetry.core.semver 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 __init__( self, name, version, pretty_version=None ): # type: (str, Union[str, VersionRange], Optional[str]) -> 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 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 supports_python2(self): return self._package.python_constraint.allows_any( parse_constraint(">=2.0.0 <3.0.0"))
def handle(self) -> int: from tomlkit import inline_table from poetry.core.semver import parse_constraint packages = self.argument("name") is_dev = self.option("dev") if self.option("extras") and len(packages) > 1: raise ValueError("You can only specify one package " "when using the --extras option") section = "dependencies" if is_dev: section = "dev-dependencies" original_content = self.poetry.file.read() content = self.poetry.file.read() poetry_content = content["tool"]["poetry"] if section not in poetry_content: poetry_content[section] = {} existing_packages = self.get_existing_packages_from_input( packages, poetry_content, section) if existing_packages: self.notify_about_existing_packages(existing_packages) packages = [name for name in packages if name not in existing_packages] if not packages: self.line("Nothing to add.") return 0 requirements = self._determine_requirements( packages, allow_prereleases=self.option("allow-prereleases"), source=self.option("source"), ) for _constraint in requirements: if "version" in _constraint: # Validate version constraint parse_constraint(_constraint["version"]) constraint = inline_table() for name, value in _constraint.items(): if name == "name": continue constraint[name] = value if self.option("optional"): constraint["optional"] = True if self.option("allow-prereleases"): constraint["allow-prereleases"] = True if self.option("extras"): extras = [] for extra in self.option("extras"): if " " in extra: extras += [e.strip() for e in extra.split(" ")] else: extras.append(extra) constraint["extras"] = self.option("extras") if self.option("python"): constraint["python"] = self.option("python") if self.option("platform"): constraint["platform"] = self.option("platform") if self.option("source"): constraint["source"] = self.option("source") if len(constraint) == 1 and "version" in constraint: constraint = constraint["version"] poetry_content[section][_constraint["name"]] = constraint try: # Write new content self.poetry.file.write(content) # Cosmetic new line self.line("") # Update packages self.reset_poetry() self._installer.set_package(self.poetry.package) self._installer.dry_run(self.option("dry-run")) self._installer.verbose(self._io.is_verbose()) self._installer.update(True) if self.option("lock"): self._installer.lock() self._installer.whitelist([r["name"] for r in requirements]) status = self._installer.run() except BaseException: # Using BaseException here as some exceptions, eg: KeyboardInterrupt, do not inherit from Exception self.poetry.file.write(original_content) raise if status != 0 or self.option("dry-run"): # Revert changes if not self.option("dry-run"): self.line_error( "\n" "<error>Failed to add packages, reverting the pyproject.toml file " "to its original content.</error>") self.poetry.file.write(original_content) return status
def find_packages(self, dependency: Dependency) -> List[Package]: """ Find packages on the remote server. """ 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): if (constraint.max is not None and constraint.max.is_prerelease() or constraint.min is not None and constraint.min.is_prerelease()): allow_prereleases = True try: info = self.get_package_info(dependency.name) except PackageNotFound: self._log( "No packages found for {} {}".format(dependency.name, str(constraint)), level="debug", ) return [] packages = [] ignored_pre_release_packages = [] for version, release in info["releases"].items(): if not release: # Bad release self._log( "No release information found for {}-{}, skipping".format( dependency.name, version), level="debug", ) continue try: package = Package(info["info"]["name"], version) except ParseVersionError: self._log( 'Unable to parse version "{}" for the {} package, skipping' .format(version, dependency.name), level="debug", ) continue if package.is_prerelease() and not allow_prereleases: if constraint.is_any(): # we need this when all versions of the package are pre-releases ignored_pre_release_packages.append(package) continue if not constraint or (constraint and constraint.allows(package.version)): packages.append(package) self._log( "{} packages found for {} {}".format(len(packages), dependency.name, str(constraint)), level="debug", ) return packages or ignored_pre_release_packages