def test_activate_activates_existing_virtualenv_no_envs_file( tmp_dir, manager, poetry, config, mocker): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) os.mkdir(os.path.join(tmp_dir, "{}-py3.7".format(venv_name))) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( "poetry.utils._compat.subprocess.Popen.communicate", side_effect=[("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) env = manager.activate("python3.7", NullIO()) m.assert_not_called() envs_file = TomlFile(Path(tmp_dir) / "envs.toml") assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" assert env.path == Path(tmp_dir) / "{}-py3.7".format(venv_name) assert env.base == Path("/prefix")
def test_activated(app, tmp_dir, config): app.poetry._config = config config.add_property("settings.virtualenvs.path", str(tmp_dir)) venv_name = EnvManager.generate_env_name("simple_project", str(app.poetry.file.parent)) (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() envs_file = TomlFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) command = app.find("env list") tester = CommandTester(command) tester.execute() expected = """\ {}-py3.6 {}-py3.7 (Activated) """.format(venv_name, venv_name) assert expected == tester.io.fetch_output()
def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( tmp_dir, manager, poetry, config, mocker): os.environ["VIRTUAL_ENV"] = "/environment/prefix" venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() envs_file = TomlFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( "poetry.utils._compat.subprocess.Popen.communicate", side_effect=[("/prefix", None)], ) env = manager.get() assert env.path == Path(tmp_dir) / "{}-py3.7".format(venv_name) assert env.base == Path("/prefix")
def test_remove_also_deactivates(tmp_dir, manager, poetry, config, mocker): config.merge({"virtualenvs": {"path": str(tmp_dir)}}) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) envs_file = TomlFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.6", "patch": "3.6.6"} envs_file.write(doc) venv = manager.remove("python3.6") assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists() envs = envs_file.read() assert venv_name not in envs
def test_pyproject_parsing(fixture): f = TomlFile(fixture.with_name('pyproject.toml')) content = f.read() assert 'dependencies' in content['tool']['poetry'] assert content['tool']['poetry']['dependencies']['python'] == '^3.6'
def test_pyproject_parsing(fixture): f = TomlFile(fixture.with_name("pyproject.toml")) content = f.read() assert "dependencies" in content["tool"]["poetry"] assert content["tool"]["poetry"]["dependencies"]["python"] == "^3.6"
def test_activate_activates_non_existing_virtualenv_no_envs_file( mocker, tester, venv_cache, venv_name, venvs_in_cache_config): mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(), ) mock_build_env = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) tester.execute("3.7") venv_py37 = venv_cache / "{}-py3.7".format(venv_name) mock_build_env.assert_called_with(venv_py37, executable="python3.7") envs_file = TomlFile(venv_cache / "envs.toml") assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" expected = """\ Creating virtualenv {} in {} Using virtualenv: {} """.format( venv_py37.name, venv_py37.parent, venv_py37, ) assert expected == tester.io.fetch_output()
def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( manager, poetry, config, tmp_dir, mocker): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] config.merge({ "virtualenvs": { "path": str(Path(tmp_dir) / "virtualenvs"), "in-project": True, } }) mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( "poetry.utils._compat.subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv") manager.activate("python3.7", NullIO()) m.assert_called_with(os.path.join(str(poetry.file.parent), ".venv"), executable="python3.7") envs_file = TomlFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") assert not envs_file.exists()
def get(self, cwd, reload=False): # type: (Path, bool) -> Env if self._env is not None and not reload: return self._env python_minor = ".".join([str(v) for v in sys.version_info[:2]]) venv_path = self._config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) envs_file = TomlFile(venv_path / self.ENVS_FILE) env = None base_env_name = self.generate_env_name(cwd.name, str(cwd)) if envs_file.exists(): envs = envs_file.read() env = envs.get(base_env_name) if env: python_minor = env["minor"] # Check if we are inside a virtualenv or not in_venv = os.environ.get("VIRTUAL_ENV") is not None if not in_venv or env is not None: # Checking if a local virtualenv exists if (cwd / ".venv").exists() and (cwd / ".venv").is_dir(): venv = cwd / ".venv" return VirtualEnv(venv) create_venv = self._config.get("virtualenvs.create", True) if not create_venv: return SystemEnv(Path(sys.prefix)) venv_path = self._config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) name = "{}-py{}".format(base_env_name, python_minor.strip()) venv = venv_path / name if not venv.exists(): return SystemEnv(Path(sys.prefix)) return VirtualEnv(venv) if os.environ.get("VIRTUAL_ENV") is not None: prefix = Path(os.environ["VIRTUAL_ENV"]) base_prefix = None else: prefix = Path(sys.prefix) base_prefix = self.get_base_prefix() return VirtualEnv(prefix, base_prefix)
def install_directory(self, package): from poetry.io import NullIO from poetry.masonry.builder import SdistBuilder from poetry.poetry import Poetry from poetry.utils._compat import decode from poetry.utils.env import NullEnv from poetry.utils.toml_file import TomlFile if package.root_dir: req = os.path.join(package.root_dir, package.source_url) else: req = os.path.realpath(package.source_url) args = ["install", "--no-deps", "-U"] pyproject = TomlFile(os.path.join(req, "pyproject.toml")) has_poetry = False has_build_system = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) # Even if there is a build system specified # pip as of right now does not support it fully # TODO: Check for pip version when proper PEP-517 support lands # has_build_system = ("build-system" in pyproject_content) setup = os.path.join(req, "setup.py") has_setup = os.path.exists(setup) if not has_setup and has_poetry and (package.develop or not has_build_system): # We actually need to rely on creating a temporary setup.py # file since pip, as of this comment, does not support # build-system for editable packages # We also need it for non-PEP-517 packages builder = SdistBuilder(Poetry.create(pyproject.parent), NullEnv(), NullIO()) with open(setup, "w", encoding="utf-8") as f: f.write(decode(builder.build_setup())) if package.develop: args.append("-e") args.append(req) if self._vendor_path: args += ["-t", self._vendor_path] try: return self.run(*args) finally: if not has_setup and os.path.exists(setup): os.remove(setup)
def publish(self, repository_name, username, password): if repository_name: self._io.writeln( "Publishing <info>{}</info> (<comment>{}</comment>) " "to <fg=cyan>{}</>".format( self._package.pretty_name, self._package.pretty_version, repository_name, )) else: self._io.writeln( "Publishing <info>{}</info> (<comment>{}</comment>) " "to <fg=cyan>PyPI</>".format(self._package.pretty_name, self._package.pretty_version)) if not repository_name: url = "https://upload.pypi.org/legacy/" repository_name = "pypi" else: # Retrieving config information config_file = TomlFile(Path(CONFIG_DIR) / "config.toml") if not config_file.exists(): raise RuntimeError("Config file does not exist. " "Unable to get repository information") config = config_file.read() if ("repositories" not in config or repository_name not in config["repositories"]): raise RuntimeError( "Repository {} is not defined".format(repository_name)) url = config["repositories"][repository_name]["url"] if not (username and password): auth = get_http_basic_auth(self._poetry.auth_config, repository_name) if auth: username = auth[0] password = auth[1] # Requesting missing credentials if not username: username = self._io.ask("Username:"******"Password:") # TODO: handle certificates self._uploader.auth(username, password) return self._uploader.upload(url)
def install_directory(self, package): from poetry.factory import Factory from poetry.utils.toml_file import TomlFile if package.root_dir: req = (package.root_dir / package.source_url).as_posix() else: req = os.path.realpath(package.source_url) args = ["install", "--no-deps", "-U"] pyproject = TomlFile(os.path.join(req, "pyproject.toml")) has_poetry = False has_build_system = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ( "tool" in pyproject_content and "poetry" in pyproject_content["tool"] ) # Even if there is a build system specified # pip as of right now does not support it fully # TODO: Check for pip version when proper PEP-517 support lands # has_build_system = ("build-system" in pyproject_content) setup = os.path.join(req, "setup.py") has_setup = os.path.exists(setup) if has_poetry and (package.develop or not has_build_system): # We actually need to rely on creating a temporary setup.py # file since pip, as of this comment, does not support # build-system for editable packages # We also need it for non-PEP-517 packages from poetry.masonry.builders.editable import EditableBuilder builder = EditableBuilder( Factory().create_poetry(pyproject.parent), self._env, NullIO() ) builder.build() return if package.develop: args.append("-e") args.append(req) try: return self.run(*args) finally: if not has_setup and os.path.exists(setup): os.remove(setup)
def test_validate_fails(): complete = TomlFile(fixtures_dir / "complete.toml") content = complete.read()["tool"]["poetry"] content["this key is not in the schema"] = "" if PY2: expected = ("Additional properties are not allowed " "(u'this key is not in the schema' was unexpected)") else: expected = ("Additional properties are not allowed " "('this key is not in the schema' was unexpected)") assert Factory.validate(content) == {"errors": [expected], "warnings": []}
def _get_poetry_package(path): # type: (Path) -> Optional[ProjectPackage] pyproject = path.joinpath("pyproject.toml") try: # Note: we ignore any setup.py file at this step if pyproject.exists(): # TODO: add support for handling non-poetry PEP-517 builds pyproject = TomlFile(pyproject) pyproject_content = pyproject.read() supports_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if supports_poetry: return Factory().create_poetry(path).package except RuntimeError: pass
def install_directory(self, package, update=False): from poetry.io import NullIO from poetry.masonry.builder import SdistBuilder from poetry.poetry import Poetry from poetry.utils._compat import decode from poetry.utils.env import NullEnv from poetry.utils.toml_file import TomlFile if package.root_dir: req = os.path.join(package.root_dir, package.source_url) else: req = os.path.realpath(package.source_url) args = ["install", "--no-deps", "-U"] pyproject = TomlFile(os.path.join(req, "pyproject.toml")) has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) has_build_system = "build-system" in pyproject_content setup = os.path.join(req, "setup.py") has_setup = os.path.exists(setup) if not has_setup and has_poetry and (package.develop or not has_build_system): # We actually need to rely on creating a temporary setup.py # file since pip, as of this comment, does not support # build-system for editable packages # We also need it for non-PEP-517 packages builder = SdistBuilder(Poetry.create(pyproject.parent), NullEnv(), NullIO()) with open(setup, "w") as f: f.write(decode(builder.build_setup())) if package.develop: args.append("-e") args.append(req) try: return self.run(*args) finally: if not has_setup and os.path.exists(setup): os.remove(setup)
def __init__(self, lock, local_config): self._lock = TomlFile(lock) self._local_config = local_config self._lock_data = None self._content_hash = self._get_content_hash() self._locked = False self._lock_data = None
def create(cls, pac_file: Path, modified=False): local_config = TomlFile(pac_file.as_posix()).read() if "package" not in local_config: raise RuntimeError("[package] section not found in {}".format( pac_file.name)) # Checking validity cls.check(local_config) name = str(pac_file.parent).replace("/", ".") version = local_config["package"]["version"] if not modified: package = Package(name, version) else: old_version = _get_old_version(pac_file) package = ModifiedPackage(name, version, old_version) package.root_dir = pac_file.parent if "dependencies" in local_config: for name, constraint in local_config["dependencies"].items(): package.add_dependency(name, constraint) if "dev-dependencies" in local_config: for name, constraint in local_config["dev-dependencies"].items(): package.add_dependency(name, constraint, category="dev") return cls(pac_file, local_config, package)
def test_activate_activates_non_existing_virtualenv_no_envs_file( app, tmp_dir, config, mocker): app.poetry._config = config if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] config.add_property("settings.virtualenvs.path", str(tmp_dir)) mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( "poetry.utils._compat.subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) command = app.find("env use") tester = CommandTester(command) tester.execute("3.7") venv_name = EnvManager.generate_env_name("simple_project", str(app.poetry.file.parent)) m.assert_called_with(os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7") envs_file = TomlFile(Path(tmp_dir) / "envs.toml") assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" expected = """\ Creating virtualenv {} in {} Using virtualenv: {} """.format( "{}-py3.7".format(venv_name), tmp_dir, os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), ) assert expected == tester.io.fetch_output()
def search_package(name): all_paths = set(Path(".").rglob("pac.toml")) found = None for path in all_paths: local_config = TomlFile(path.as_posix()).read() if local_config["package"]["name"] == name: found = Pac.create(path) return found
def __init__(self, file, local_config, package, locker, config): self._file = TomlFile(file) self._package = package self._local_config = local_config self._locker = Locker(locker.lock.path, locker._local_config) self._config = config # Configure sources self._pool = Pool()
def __init__( self, name, path, # type: Path category="main", # type: str optional=False, # type: bool base=None, # type: Path develop=True, # type: bool ): self._path = path self._base = base self._full_path = path self._develop = develop self._supports_poetry = False 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 determine actions setup = self._full_path / "setup.py" pyproject = TomlFile(self._full_path / "pyproject.toml") if pyproject.exists(): pyproject_content = pyproject.read() self._supports_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if not setup.exists() and not self._supports_poetry: raise ValueError( "Directory {} does not seem to be a Python package".format( self._full_path)) super(DirectoryDependency, self).__init__(name, "*", category=category, optional=optional, allows_prereleases=True)
def __init__(self, file, local_config, package, locker): self._file = TomlFile(file) self._package = package self._local_config = local_config self._locker = Locker(locker.lock.path, locker._local_config) self._config = Config.create("config.toml") self._auth_config = Config.create("auth.toml") # Configure sources self._pool = Pool()
def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( app, tmp_dir, mocker ): mocker.stopall() os.environ["VIRTUAL_ENV"] = "/environment/prefix" venv_name = EnvManager.generate_env_name( "simple-project", str(app.poetry.file.parent) ) current_python = sys.version_info[:3] python_minor = ".".join(str(v) for v in current_python[:2]) python_patch = ".".join(str(v) for v in current_python) app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}}) (Path(tmp_dir) / "{}-py{}".format(venv_name, python_minor)).mkdir() envs_file = TomlFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": python_minor, "patch": python_patch} envs_file.write(doc) mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(Version(*current_python)), ) mocker.patch( "poetry.utils._compat.subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) command = app.find("env use") tester = CommandTester(command) tester.execute(python_minor) expected = """\ Using virtualenv: {} """.format( os.path.join(tmp_dir, "{}-py{}".format(venv_name, python_minor)) ) assert expected == tester.io.fetch_output()
def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( tester, current_python, venv_cache, venv_name, venvs_in_cache_config): os.environ["VIRTUAL_ENV"] = "/environment/prefix" python_minor = ".".join(str(v) for v in current_python[:2]) python_patch = ".".join(str(v) for v in current_python[:3]) venv_dir = venv_cache / "{}-py{}".format(venv_name, python_minor) venv_dir.mkdir(parents=True, exist_ok=True) envs_file = TomlFile(venv_cache / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": python_minor, "patch": python_patch} envs_file.write(doc) tester.execute(python_minor) expected = """\ Using virtualenv: {} """.format(venv_dir) assert expected == tester.io.fetch_output()
def test_deactivate_activated(tmp_dir, manager, poetry, config, mocker): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) version = Version.parse(".".join(str(c) for c in sys.version_info[:3])) other_version = Version.parse("3.4") if version.major == 2 else version.next_minor ( Path(tmp_dir) / "{}-py{}.{}".format(venv_name, version.major, version.minor) ).mkdir() ( Path(tmp_dir) / "{}-py{}.{}".format(venv_name, other_version.major, other_version.minor) ).mkdir() envs_file = TomlFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = { "minor": "{}.{}".format(other_version.major, other_version.minor), "patch": other_version.text, } envs_file.write(doc) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(), ) manager.deactivate(NullIO()) env = manager.get() assert env.path == Path(tmp_dir) / "{}-py{}.{}".format( venv_name, version.major, version.minor ) assert Path("/prefix") envs = envs_file.read() assert len(envs) == 0
def test_toml_file(fixture): f = TomlFile(fixture) content = f.read() assert content[""]["title"] == "TOML Example" assert content["owner"]["name"] == "Tom Preston-Werner" assert isinstance(content["owner"], dict) assert isinstance(content["database"]["ports"], list) assert content["database"]["ports"] == [8001, 8001, 8002] assert content["database"]["connection_max"] == 5000 assert content["database"]["enabled"] servers = content["servers"] assert len(servers) == 2 alpha = servers["alpha"] assert len(alpha) == 2 assert alpha["ip"] == "10.0.0.1" assert alpha["dc"] == "eqdc10" beta = servers["beta"] assert len(beta) == 2 assert beta["ip"] == "10.0.0.2" assert beta["dc"] == "eqdc10" clients = content["clients"] assert len(clients["data"]) == 2 assert clients["data"] == [["gamma", "delta"], [1, 2]] assert clients["hosts"] == ["alpha", "omega"] assert clients["str_multiline"] == "Roses are red\nViolets are blue" fruits = content["fruit"] assert len(fruits) == 2 apple = fruits[0] assert len(apple) == 3 banana = fruits[1] assert len(banana["variety"][0]["points"]) == 3
def test_toml_file(fixture): f = TomlFile(fixture) content = f.read() assert content['']['title'] == 'TOML Example' assert content['owner']['name'] == 'Tom Preston-Werner' assert isinstance(content['owner'], dict) assert isinstance(content['database']['ports'], list) assert content['database']['ports'] == [8001, 8001, 8002] assert content['database']['connection_max'] == 5000 assert content['database']['enabled'] servers = content['servers'] assert len(servers) == 2 alpha = servers['alpha'] assert len(alpha) == 2 assert alpha['ip'] == '10.0.0.1' assert alpha['dc'] == 'eqdc10' beta = servers['beta'] assert len(beta) == 2 assert beta['ip'] == '10.0.0.2' assert beta['dc'] == 'eqdc10' clients = content['clients'] assert len(clients['data']) == 2 assert clients['data'] == [['gamma', 'delta'], [1, 2]] assert clients['hosts'] == ['alpha', 'omega'] assert clients['str_multiline'] == 'Roses are red\nViolets are blue' fruits = content['fruit'] assert len(fruits) == 2 apple = fruits[0] assert len(apple) == 3 banana = fruits[1] assert len(banana['variety'][0]['points']) == 3
def test_activate_activates_recreates_for_different_patch( tmp_dir, manager, poetry, config, mocker ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) envs_file = TomlFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) os.mkdir(os.path.join(tmp_dir, "{}-py3.7".format(venv_name))) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( "poetry.utils._compat.subprocess.Popen.communicate", side_effect=[ ("/prefix", None), ('{"version_info": [3, 7, 0]}', None), ("/prefix", None), ("/prefix", None), ("/prefix", None), ], ) build_venv_m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=build_venv ) remove_venv_m = mocker.patch( "poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv ) env = manager.activate("python3.7", NullIO()) build_venv_m.assert_called_with( os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7" ) remove_venv_m.assert_called_with( os.path.join(tmp_dir, "{}-py3.7".format(venv_name)) ) assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" assert env.path == Path(tmp_dir) / "{}-py3.7".format(venv_name) assert env.base == Path("/prefix") assert (Path(tmp_dir) / "{}-py3.7".format(venv_name)).exists()
def handle(self): # Load poetry config and display errors, if any poetry_file = Poetry.locate(Path.cwd()) config = TomlFile(str(poetry_file)).read()["tool"]["poetry"] check_result = Poetry.check(config, strict=True) if not check_result["errors"] and not check_result["warnings"]: self.info("All set!") return 0 for error in check_result["errors"]: self.line("<error>Error: {}</error>".format(error)) for error in check_result["warnings"]: self.line("<warning>Warning: {}</warning>".format(error)) return 1
def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = EnvManager.generate_env_name("simple_project", str(CWD)) envs_file = TomlFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) os.mkdir(os.path.join(tmp_dir, "{}-py3.7".format(venv_name))) os.mkdir(os.path.join(tmp_dir, "{}-py3.6".format(venv_name))) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "poetry.utils._compat.subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) mocker.patch( "poetry.utils._compat.subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) build_venv_m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=build_venv ) remove_venv_m = mocker.patch( "poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv ) env = EnvManager(config).activate("python3.6", CWD, NullIO()) build_venv_m.assert_not_called() remove_venv_m.assert_not_called() assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.6" assert envs[venv_name]["patch"] == "3.6.6" assert env.path == Path(tmp_dir) / "{}-py3.6".format(venv_name) assert env.base == Path("/prefix") assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
def __init__(self, lock, local_config): # type: (Path, dict) -> None self._lock = TomlFile(lock) self._local_config = local_config self._lock_data = None self._content_hash = self._get_content_hash()
class Locker: _relevant_keys = ["dependencies", "dev-dependencies", "source", "extras"] def __init__(self, lock, local_config): # type: (Path, dict) -> None self._lock = TomlFile(lock) self._local_config = local_config self._lock_data = None self._content_hash = self._get_content_hash() @property def lock(self): # type: () -> TomlFile return self._lock @property def lock_data(self): if self._lock_data is None: self._lock_data = self._get_lock_data() return self._lock_data def is_locked(self): # type: () -> bool """ Checks whether the locker has been locked (lockfile found). """ if not self._lock.exists(): return False return "package" in self.lock_data def is_fresh(self): # type: () -> bool """ Checks whether the lock file is still up to date with the current hash. """ lock = self._lock.read() metadata = lock.get("metadata", {}) if "content-hash" in metadata: return self._content_hash == lock["metadata"]["content-hash"] return False def locked_repository( self, with_dev_reqs=False ): # type: (bool) -> poetry.repositories.Repository """ Searches and returns a repository of locked packages. """ if not self.is_locked(): return poetry.repositories.Repository() lock_data = self.lock_data packages = poetry.repositories.Repository() if with_dev_reqs: locked_packages = lock_data["package"] else: locked_packages = [ p for p in lock_data["package"] if p["category"] == "main" ] if not locked_packages: return packages for info in locked_packages: package = poetry.packages.Package( info["name"], info["version"], info["version"] ) package.description = info.get("description", "") package.category = info["category"] package.optional = info["optional"] package.hashes = lock_data["metadata"]["hashes"][info["name"]] package.python_versions = info["python-versions"] for dep_name, constraint in info.get("dependencies", {}).items(): if isinstance(constraint, list): for c in constraint: package.add_dependency(dep_name, c) continue package.add_dependency(dep_name, constraint) if "requirements" in info: package.requirements = info["requirements"] if "source" in info: package.source_type = info["source"]["type"] package.source_url = info["source"]["url"] package.source_reference = info["source"]["reference"] packages.add_package(package) return packages def set_lock_data(self, root, packages): # type: () -> bool hashes = {} packages = self._lock_packages(packages) # Retrieving hashes for package in packages: if package["name"] not in hashes: hashes[package["name"]] = [] hashes[package["name"]] += package["hashes"] del package["hashes"] lock = document() lock["package"] = packages if root.extras: lock["extras"] = { extra: [dep.pretty_name for dep in deps] for extra, deps in root.extras.items() } lock["metadata"] = { "python-versions": root.python_versions, "platform": root.platform, "content-hash": self._content_hash, "hashes": hashes, } if not self.is_locked() or lock != self.lock_data: self._write_lock_data(lock) return True return False def _write_lock_data(self, data): self.lock.write(data) # Checking lock file data consistency if data != self.lock.read(): raise RuntimeError("Inconsistent lock file data.") self._lock_data = None def _get_content_hash(self): # type: () -> str """ Returns the sha256 hash of the sorted content of the composer file. """ content = self._local_config relevant_content = {} for key in self._relevant_keys: relevant_content[key] = content.get(key) content_hash = sha256( json.dumps(relevant_content, sort_keys=True).encode() ).hexdigest() return content_hash def _get_lock_data(self): # type: () -> dict if not self._lock.exists(): raise RuntimeError("No lockfile found. Unable to read locked packages") return self._lock.read() def _lock_packages( self, packages ): # type: (List['poetry.packages.Package']) -> list locked = [] for package in sorted(packages, key=lambda x: x.name): spec = self._dump_package(package) locked.append(spec) return locked def _dump_package(self, package): # type: (poetry.packages.Package) -> dict dependencies = {} for dependency in sorted(package.requires, key=lambda d: d.name): if dependency.is_optional() and not dependency.is_activated(): continue if dependency.pretty_name not in dependencies: dependencies[dependency.pretty_name] = [] constraint = {"version": str(dependency.pretty_constraint)} if not dependency.python_constraint.is_any(): constraint["python"] = str(dependency.python_constraint) if dependency.platform != "*": constraint["platform"] = dependency.platform if len(constraint) == 1: dependencies[dependency.pretty_name].append(constraint["version"]) else: dependencies[dependency.pretty_name].append(constraint) for name, constraints in dependencies.items(): if len(constraints) == 1: dependencies[name] = constraints[0] data = { "name": package.pretty_name, "version": package.pretty_version, "description": package.description, "category": package.category, "optional": package.optional, "python-versions": package.python_versions, "platform": package.platform, "hashes": sorted(package.hashes), } if dependencies: for k, constraints in dependencies.items(): if len(constraints) == 1: dependencies[k] = constraints[0] data["dependencies"] = dependencies if package.source_type: data["source"] = { "type": package.source_type, "url": package.source_url, "reference": package.source_reference, } if package.requirements: data["requirements"] = package.requirements return data
def test_check(): complete = TomlFile(fixtures_dir / "complete.toml") content = complete.read(raw=True)["tool"]["poetry"] assert Poetry.check(content)
def publish(self, repository_name, username, password): if repository_name: self._io.writeln( "Publishing <info>{}</info> (<comment>{}</comment>) " "to <fg=cyan>{}</>".format( self._package.pretty_name, self._package.pretty_version, repository_name, ) ) else: self._io.writeln( "Publishing <info>{}</info> (<comment>{}</comment>) " "to <fg=cyan>PyPI</>".format( self._package.pretty_name, self._package.pretty_version ) ) if not repository_name: url = "https://upload.pypi.org/legacy/" repository_name = "pypi" else: # Retrieving config information config_file = TomlFile(Path(CONFIG_DIR) / "config.toml") if not config_file.exists(): raise RuntimeError( "Config file does not exist. " "Unable to get repository information" ) config = config_file.read() if ( "repositories" not in config or repository_name not in config["repositories"] ): raise RuntimeError( "Repository {} is not defined".format(repository_name) ) url = config["repositories"][repository_name]["url"] if not (username and password): auth = get_http_basic_auth(repository_name) if auth: username = auth[0] password = auth[1] # Requesting missing credentials if not username: username = self._io.ask("Username:"******"Password:") # TODO: handle certificates self._uploader.auth(username, password) return self._uploader.upload(url)
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] """ Search for the specifications that match the given VCS dependency. Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ if dependency.vcs != "git": raise ValueError("Unsupported VCS dependency {}".format(dependency.vcs)) tmp_dir = Path(mkdtemp(prefix="pypoetry-git-{}".format(dependency.name))) try: git = Git() git.clone(dependency.source, tmp_dir) git.checkout(dependency.reference, tmp_dir) revision = git.rev_parse(dependency.reference, tmp_dir).strip() if dependency.tag or dependency.rev: revision = dependency.reference pyproject = TomlFile(tmp_dir / "pyproject.toml") pyproject_content = None has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ( "tool" in pyproject_content and "poetry" in pyproject_content["tool"] ) if pyproject_content and has_poetry: # If a pyproject.toml file exists # We use it to get the information we need info = pyproject_content["tool"]["poetry"] name = info["name"] version = info["version"] package = Package(name, version, version) package.source_type = dependency.vcs package.source_url = dependency.source package.source_reference = dependency.reference for req_name, req_constraint in info["dependencies"].items(): if req_name == "python": package.python_versions = req_constraint continue package.add_dependency(req_name, req_constraint) else: # We need to use setup.py here # to figure the information we need # We need to place ourselves in the proper # folder for it to work venv = Venv.create(self._io) current_dir = os.getcwd() os.chdir(tmp_dir.as_posix()) try: venv.run("python", "setup.py", "egg_info") # Sometimes pathlib will fail on recursive # symbolic links, so we need to workaround it # and use the glob module instead. # Note that this does not happen with pathlib2 # so it's safe to use it for Python < 3.4. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(tmp_dir), "**", "*.egg-info"), recursive=True, ) ) else: egg_info = next(tmp_dir.glob("**/*.egg-info")) 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) for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) package.requires.append(dep) except Exception: raise finally: os.chdir(current_dir) package.source_type = "git" package.source_url = dependency.source package.source_reference = revision except Exception: raise finally: shutil.rmtree(tmp_dir.as_posix()) if dependency.name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}".format( dependency.name, package.name ) ) if dependency.extras: for extra in dependency.extras: if extra in package.extras: for dep in package.extras[extra]: dep.activate() return [package]
def fixture(name): file = TomlFile(Path(__file__).parent / "fixtures" / "{}.test".format(name)) return file.read()
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(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())) 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_check_fails(): complete = TomlFile(fixtures_dir / "complete.toml") content = complete.read()["tool"]["poetry"] content["this key is not in the schema"] = "" with pytest.raises(InvalidProjectFile): Poetry.check(content)