def __init__(self, e): # type: (CalledProcessError) -> None message = "Command {} errored with the following output: \n{}".format( e.cmd, decode(e.output)) super(VenvCommandError, self).__init__(message)
def test_complete(): module_path = fixtures_dir / "complete" builder = CompleteBuilder(Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()) builder.build() whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl" assert whl.exists() if sys.platform != "win32": assert (os.stat(str(whl)).st_mode & 0o777) == 0o644 zip = zipfile.ZipFile(str(whl)) try: assert "my_package/sub_pgk1/extra_file.xml" not in zip.namelist() entry_points = zip.read("my_package-1.2.3.dist-info/entry_points.txt") assert (decode(entry_points.decode()) == """\ [console_scripts] extra-script=my_package.extra:main[time] my-2nd-script=my_package:main2 my-script=my_package:main """) wheel_data = decode(zip.read("my_package-1.2.3.dist-info/WHEEL")) assert (wheel_data == """\ Wheel-Version: 1.0 Generator: poetry {} Root-Is-Purelib: true Tag: py3-none-any """.format(__version__)) wheel_data = decode(zip.read("my_package-1.2.3.dist-info/METADATA")) assert (wheel_data == """\ Metadata-Version: 2.1 Name: my-package Version: 1.2.3 Summary: Some description. Home-page: https://poetry.eustace.io/ License: MIT Keywords: packaging,dependency,poetry Author: Sébastien Eustace Author-email: [email protected] Maintainer: People Everywhere Maintainer-email: [email protected] Requires-Python: >=3.6,<4.0 Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: time Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0) Requires-Dist: cleo (>=0.6,<0.7) Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time") Project-URL: Documentation, https://poetry.eustace.io/docs Project-URL: Issue Tracker, https://github.com/sdispater/poetry/issues Project-URL: Repository, https://github.com/sdispater/poetry Description-Content-Type: text/x-rst My Package ========== """) finally: zip.close()
def create_venv(self, io, name=None, executable=None, force=False ): # type: (IO, Optional[str], Optional[str], bool) -> Env if self._env is not None and not force: return self._env cwd = self._poetry.file.parent env = self.get(reload=True) if not env.is_sane(): force = True if env.is_venv() and not force: # Already inside a virtualenv. return env create_venv = self._poetry.config.get("virtualenvs.create") root_venv = self._poetry.config.get("virtualenvs.in-project") venv_path = self._poetry.config.get("virtualenvs.path") if root_venv: venv_path = cwd / ".venv" elif venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) if not name: name = self._poetry.package.name python_patch = ".".join([str(v) for v in sys.version_info[:3]]) python_minor = ".".join([str(v) for v in sys.version_info[:2]]) if executable: python_patch = decode( subprocess.check_output( list_to_shell_command([ executable, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", ]), shell=True, ).strip()) python_minor = ".".join(python_patch.split(".")[:2]) supported_python = self._poetry.package.python_constraint if not supported_python.allows(Version.parse(python_patch)): # The currently activated or chosen Python version # is not compatible with the Python constraint specified # for the project. # If an executable has been specified, we stop there # and notify the user of the incompatibility. # Otherwise, we try to find a compatible Python version. if executable: raise NoCompatiblePythonVersionFound( self._poetry.package.python_versions, python_patch) io.write_line( "<warning>The currently activated Python version {} " "is not supported by the project ({}).\n" "Trying to find and use a compatible version.</warning> ". format(python_patch, self._poetry.package.python_versions)) for python_to_try in reversed( sorted( self._poetry.package.AVAILABLE_PYTHONS, key=lambda v: (v.startswith("3"), -len(v), v), )): if len(python_to_try) == 1: if not parse_constraint("^{}.0".format( python_to_try)).allows_any(supported_python): continue elif not supported_python.allows_all( parse_constraint(python_to_try + ".*")): continue python = "python" + python_to_try if io.is_debug(): io.write_line("<debug>Trying {}</debug>".format(python)) try: python_patch = decode( subprocess.check_output( list_to_shell_command([ python, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", ]), stderr=subprocess.STDOUT, shell=True, ).strip()) except CalledProcessError: continue if not python_patch: continue if supported_python.allows(Version.parse(python_patch)): io.write_line("Using <c1>{}</c1> ({})".format( python, python_patch)) executable = python python_minor = ".".join(python_patch.split(".")[:2]) break if not executable: raise NoCompatiblePythonVersionFound( self._poetry.package.python_versions) if root_venv: venv = venv_path else: name = self.generate_env_name(name, str(cwd)) name = "{}-py{}".format(name, python_minor.strip()) venv = venv_path / name if not venv.exists(): if create_venv is False: io.write_line("<fg=black;bg=yellow>" "Skipping virtualenv creation, " "as specified in config file." "</>") return SystemEnv(Path(sys.prefix)) io.write_line("Creating virtualenv <c1>{}</> in {}".format( name, str(venv_path))) self.build_venv( venv, executable=executable, flags=self._poetry.config.get("virtualenvs.options"), ) else: if force: if not env.is_sane(): io.write_line( "<warning>The virtual environment found in {} seems to be broken.</warning>" .format(env.path)) io.write_line("Recreating virtualenv <c1>{}</> in {}".format( name, str(venv))) self.remove_venv(venv) self.build_venv( venv, executable=executable, flags=self._poetry.config.get("virtualenvs.options"), ) elif io.is_very_verbose(): io.write_line( "Virtualenv <c1>{}</> already exists.".format(name)) # venv detection: # stdlib venv may symlink sys.executable, so we can't use realpath. # but others can symlink *to* the venv Python, # so we can't just use sys.executable. # So we just check every item in the symlink tree (generally <= 3) p = os.path.normcase(sys.executable) paths = [p] while os.path.islink(p): p = os.path.normcase( os.path.join(os.path.dirname(p), os.readlink(p))) paths.append(p) p_venv = os.path.normcase(str(venv)) if any(p.startswith(p_venv) for p in paths): # Running properly in the virtualenv, don't need to do anything return SystemEnv(Path(sys.prefix), self.get_base_prefix()) return VirtualEnv(venv)
def _write_metadata_file(self, fp): """ Write out metadata in the 2.x format (email like) """ fp.write(decode(self.get_metadata_content()))
def activate(self, python, io): # type: (str, IO) -> Env venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) cwd = self._poetry.file.parent envs_file = TOMLFile(venv_path / self.ENVS_FILE) try: python_version = Version.parse(python) python = "python{}".format(python_version.major) if python_version.precision > 1: python += ".{}".format(python_version.minor) except ValueError: # Executable in PATH or full executable path pass try: python_version = decode( subprocess.check_output( list_to_shell_command([ python, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", ]), shell=True, )) except CalledProcessError as e: raise EnvCommandError(e) python_version = Version.parse(python_version.strip()) minor = "{}.{}".format(python_version.major, python_version.minor) patch = python_version.text create = False is_root_venv = self._poetry.config.get("virtualenvs.in-project") # If we are required to create the virtual environment in the root folder, # create or recreate it if needed if is_root_venv: create = False venv = self._poetry.file.parent / ".venv" if venv.exists(): # We need to check if the patch version is correct _venv = VirtualEnv(venv) current_patch = ".".join( str(v) for v in _venv.version_info[:3]) if patch != current_patch: create = True self.create_venv(io, executable=python, force=create) return self.get(reload=True) envs = tomlkit.document() base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd)) if envs_file.exists(): envs = envs_file.read() current_env = envs.get(base_env_name) if current_env is not None: current_minor = current_env["minor"] current_patch = current_env["patch"] if current_minor == minor and current_patch != patch: # We need to recreate create = True name = "{}-py{}".format(base_env_name, minor) venv = venv_path / name # Create if needed if not venv.exists() or venv.exists() and create: in_venv = os.environ.get("VIRTUAL_ENV") is not None if in_venv or not venv.exists(): create = True if venv.exists(): # We need to check if the patch version is correct _venv = VirtualEnv(venv) current_patch = ".".join( str(v) for v in _venv.version_info[:3]) if patch != current_patch: create = True self.create_venv(io, executable=python, force=create) # Activate envs[base_env_name] = {"minor": minor, "patch": patch} envs_file.write(envs) return self.get(reload=True)
def remove(self, python): # type: (str) -> Env venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) cwd = self._poetry.file.parent envs_file = TOMLFile(venv_path / self.ENVS_FILE) base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd)) if python.startswith(base_env_name): venvs = self.list() for venv in venvs: if venv.path.name == python: # Exact virtualenv name if not envs_file.exists(): self.remove_venv(venv.path) return venv venv_minor = ".".join( str(v) for v in venv.version_info[:2]) base_env_name = self.generate_env_name(cwd.name, str(cwd)) envs = envs_file.read() current_env = envs.get(base_env_name) if not current_env: self.remove_venv(venv.path) return venv if current_env["minor"] == venv_minor: del envs[base_env_name] envs_file.write(envs) self.remove_venv(venv.path) return venv raise ValueError( '<warning>Environment "{}" does not exist.</warning>'.format( python)) try: python_version = Version.parse(python) python = "python{}".format(python_version.major) if python_version.precision > 1: python += ".{}".format(python_version.minor) except ValueError: # Executable in PATH or full executable path pass try: python_version = decode( subprocess.check_output( list_to_shell_command([ python, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", ]), shell=True, )) except CalledProcessError as e: raise EnvCommandError(e) python_version = Version.parse(python_version.strip()) minor = "{}.{}".format(python_version.major, python_version.minor) name = "{}-py{}".format(base_env_name, minor) venv = venv_path / name if not venv.exists(): raise ValueError( '<warning>Environment "{}" does not exist.</warning>'.format( name)) if envs_file.exists(): envs = envs_file.read() current_env = envs.get(base_env_name) if current_env is not None: current_minor = current_env["minor"] if current_minor == minor: del envs[base_env_name] envs_file.write(envs) self.remove_venv(venv) return VirtualEnv(venv)
def test_complete_no_vcs(): # Copy the complete fixtures dir to a temporary directory module_path = fixtures_dir / "complete" temporary_dir = Path(tempfile.mkdtemp()) / "complete" shutil.copytree(module_path.as_posix(), temporary_dir.as_posix()) builder = CompleteBuilder(Poetry.create(temporary_dir), NullEnv(execute=True), NullIO()) builder.build() whl = temporary_dir / "dist" / "my_package-1.2.3-py3-none-any.whl" assert whl.exists() zip = zipfile.ZipFile(str(whl)) # Check the zipped file to be sure that included and excluded files are # correctly taken account of without vcs expected_name_list = [ "my_package/__init__.py", "my_package/data1/test.json", "my_package/sub_pkg1/__init__.py", "my_package/sub_pkg2/__init__.py", "my_package/sub_pkg2/data2/data.json", "my_package-1.2.3.dist-info/entry_points.txt", "my_package-1.2.3.dist-info/LICENSE", "my_package-1.2.3.dist-info/WHEEL", "my_package-1.2.3.dist-info/METADATA", "my_package-1.2.3.dist-info/RECORD", ] assert sorted(zip.namelist()) == sorted(expected_name_list) try: entry_points = zip.read("my_package-1.2.3.dist-info/entry_points.txt") assert (decode(entry_points.decode()) == """\ [console_scripts] extra-script=my_package.extra:main[time] my-2nd-script=my_package:main2 my-script=my_package:main """) wheel_data = decode(zip.read("my_package-1.2.3.dist-info/WHEEL")) assert (wheel_data == """\ Wheel-Version: 1.0 Generator: poetry {} Root-Is-Purelib: true Tag: py3-none-any """.format(__version__)) wheel_data = decode(zip.read("my_package-1.2.3.dist-info/METADATA")) assert (wheel_data == """\ Metadata-Version: 2.1 Name: my-package Version: 1.2.3 Summary: Some description. Home-page: https://poetry.eustace.io/ License: MIT Keywords: packaging,dependency,poetry Author: Sébastien Eustace Author-email: [email protected] Requires-Python: >=3.6,<4.0 Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: time Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0) Requires-Dist: cleo (>=0.6,<0.7) Requires-Dist: pendulum (>=1.4,<2.0); extra == "time" Project-URL: Documentation, https://poetry.eustace.io/docs Project-URL: Repository, https://github.com/sdispater/poetry Description-Content-Type: text/x-rst My Package ========== """) finally: zip.close()
def demo_setup_complex_pep517_legacy(demo_setup_complex: Path) -> Path: pyproject_toml = demo_setup_complex / "pyproject.toml" pyproject_toml.write_text( decode("[build-system]\n" 'requires = ["setuptools", "wheel"]')) yield demo_setup_complex
def handle(self): from poetry.__version__ import __version__ from poetry.repositories.pypi_repository import PyPiRepository from poetry.semver import Version from poetry.utils._compat import Path from poetry.utils._compat import decode current = Path(__file__) try: current.relative_to(self.home) except ValueError: raise RuntimeError( "Poetry was not installed with the recommended installer. " "Cannot update automatically." ) version = self.argument("version") if not version: version = ">=" + __version__ repo = PyPiRepository(fallback=False) packages = repo.find_packages( "poetry", version, allow_prereleases=self.option("preview") ) if not packages: self.line("No release found for the specified version") return packages.sort( key=cmp_to_key( lambda x, y: 0 if x.version == y.version else int(x.version < y.version or -1) ) ) release = None for package in packages: if package.is_prerelease(): if self.option("preview"): release = package break continue release = package break if release is None: self.line("No new release found") return if release.version == Version.parse(__version__): self.line("You are using the latest version") return try: self.update(release) except subprocess.CalledProcessError as e: self.line("") self.output.block( [ "[CalledProcessError]", "An error has occured: {}".format(str(e)), decode(e.output), ], style="error", ) return e.returncode
def __init__( self, path, # type: Path category="main", # type: str optional=False, # type: bool base=None, # type: Path develop=False, # type: bool ): from . import dependency_from_pep_508 from .package import Package self._path = path self._base = base self._full_path = path self._develop = develop if self._base and not self._path.is_absolute(): self._full_path = self._base / self._path if not self._full_path.exists(): raise ValueError("Directory {} does not exist".format(self._path)) if self._full_path.is_file(): raise ValueError("{} is a file, expected a directory".format(self._path)) # Checking content to dertermine actions setup = self._full_path / "setup.py" pyproject = TomlFile(self._full_path / "pyproject.toml") has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ( "tool" in pyproject_content and "poetry" in pyproject_content["tool"] ) if not setup.exists() and not has_poetry: raise ValueError( "Directory {} does not seem to be a Python package".format( self._full_path ) ) if has_poetry: from poetry.masonry.builders import SdistBuilder from poetry.poetry import Poetry poetry = Poetry.create(self._full_path) builder = SdistBuilder(poetry, NullVenv(), NullIO()) with setup.open("w") as f: f.write(decode(builder.build_setup())) package = poetry.package self._package = Package(package.pretty_name, package.version) self._package.requires += package.requires self._package.dev_requires += package.dev_requires self._package.python_versions = package.python_versions self._package.platform = package.platform else: # Execute egg_info current_dir = os.getcwd() os.chdir(str(self._full_path)) try: cwd = base venv = Venv.create(NullIO(), cwd=cwd) venv.run("python", "setup.py", "egg_info") finally: os.chdir(current_dir) egg_info = list(self._full_path.glob("*.egg-info"))[0] meta = pkginfo.UnpackedSDist(str(egg_info)) if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) package = Package(meta.name, meta.version) package.description = meta.summary for req in reqs: package.requires.append(dependency_from_pep_508(req)) if meta.requires_python: package.python_versions = meta.requires_python if meta.platforms: platforms = [p for p in meta.platforms if p.lower() != "unknown"] if platforms: package.platform = " || ".join(platforms) self._package = package self._package.source_type = "directory" self._package.source_url = self._path.as_posix() super(DirectoryDependency, self).__init__( self._package.name, self._package.version, category=category, optional=optional, allows_prereleases=True, )
def test_prepare_metadata_for_build_wheel(): entry_points = """\ [console_scripts] extra-script=my_package.extra:main[time] my-2nd-script=my_package:main2 my-script=my_package:main """ wheel_data = """\ Wheel-Version: 1.0 Generator: poetry {} Root-Is-Purelib: true Tag: py3-none-any """.format(__version__) metadata = """\ Metadata-Version: 2.1 Name: my-package Version: 1.2.3 Summary: Some description. Home-page: https://poetry.eustace.io/ License: MIT Keywords: packaging,dependency,poetry Author: Sébastien Eustace Author-email: [email protected] Requires-Python: >=3.6,<4.0 Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: time Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0) Requires-Dist: cleo (>=0.6,<0.7) Requires-Dist: pendulum (>=1.4,<2.0); extra == "time" Project-URL: Documentation, https://poetry.eustace.io/docs Project-URL: Repository, https://github.com/sdispater/poetry Description-Content-Type: text/x-rst My Package ========== """ with temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "complete")): dirname = api.prepare_metadata_for_build_wheel(tmp_dir) assert "my_package-1.2.3.dist-info" == dirname dist_info = Path(tmp_dir, dirname) assert (dist_info / "entry_points.txt").exists() assert (dist_info / "WHEEL").exists() assert (dist_info / "METADATA").exists() with (dist_info / "entry_points.txt").open() as f: assert entry_points == decode(f.read()) with (dist_info / "WHEEL").open() as f: assert wheel_data == decode(f.read()) with (dist_info / "METADATA").open() as f: assert metadata == decode(f.read())
def run(self, *args): # type: (...) -> str return decode( subprocess.check_output(["git"] + list(args), stderr=subprocess.STDOUT) )
def __init__( self, path, # type: Path category='main', # type: str optional=False, # type: bool base=None, # type: Path develop=False # type: bool ): from . import dependency_from_pep_508 self._path = path self._base = base self._full_path = path self._develop = develop if self._base and not self._path.is_absolute(): self._full_path = self._base / self._path if not self._full_path.exists(): raise ValueError('Directory {} does not exist'.format(self._path)) if self._full_path.is_file(): raise ValueError('{} is a file, expected a directory'.format( self._path)) # Checking content to dertermine actions setup = self._full_path / 'setup.py' pyproject = TomlFile(self._full_path / 'pyproject.toml') has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read(True) has_poetry = ('tool' in pyproject_content and 'poetry' in pyproject_content['tool']) if not setup.exists() and not has_poetry: raise ValueError( 'Directory {} does not seem to be a Python package'.format( self._full_path)) if has_poetry: from poetry.masonry.builders import SdistBuilder from poetry.poetry import Poetry poetry = Poetry.create(self._full_path) builder = SdistBuilder(poetry, NullVenv(), NullIO()) with setup.open('w') as f: f.write(decode(builder.build_setup())) self._package = poetry.package else: from poetry.packages import Package # Execute egg_info current_dir = os.getcwd() os.chdir(str(self._full_path)) try: cwd = base venv = Venv.create(NullIO(), cwd=cwd) venv.run('python', 'setup.py', 'egg_info') finally: os.chdir(current_dir) egg_info = list(self._full_path.glob('*.egg-info'))[0] meta = pkginfo.UnpackedSDist(str(egg_info)) if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / 'requires.txt' if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) package = Package(meta.name, meta.version) package.description = meta.summary for req in reqs: package.requires.append(dependency_from_pep_508(req)) if meta.requires_python: package.python_versions = meta.requires_python if meta.platforms: platforms = [ p for p in meta.platforms if p.lower() != 'unknown' ] if platforms: package.platform = ' || '.join(platforms) self._package = package self._package.source_type = 'directory' self._package.source_reference = str(self._path) super(DirectoryDependency, self).__init__(self._package.name, self._package.version, category=category, optional=optional, allows_prereleases=True)