def test_import_another_sitecustomize(project, invoke, capfd): project.meta["requires-python"] = ">=2.7" project.write_pyproject() # a script for checking another sitecustomize is imported project.root.joinpath("foo.py").write_text( textwrap.dedent(""" import sys module = sys.modules.get('another_sitecustomize') if module: print(module.__file__) """)) # ensure there have at least one sitecustomize can be imported # there may have more than one sitecustomize.py in sys.path project.root.joinpath("sitecustomize.py").write_text("# do nothing") env = os.environ.copy() paths = [str(project.root)] original_paths = env.get("PYTHONPATH", "") if original_paths: paths.insert(0, original_paths) env["PYTHONPATH"] = os.pathsep.join(paths) # invoke pdm commands invoke = functools.partial(invoke, env=env, obj=project) project._environment = None capfd.readouterr() with cd(project.root): invoke(["run", "python", "foo.py"]) # only the first and second sitecustomize module will be imported # as sitecustomize and another_sitecustomize # the first one is pdm.pep582.sitecustomize for sure # the second one maybe not the dummy module injected here out, _ = capfd.readouterr() assert out.strip()
def test_convert_poetry(project): golden_file = FIXTURES / "pyproject-poetry.toml" assert poetry.check_fingerprint(project, golden_file) with cd(FIXTURES): result, settings = poetry.convert( project, golden_file, Namespace(dev=False, group=None) ) assert result["authors"][0] == { "name": "Sébastien Eustace", "email": "*****@*****.**", } assert result["name"] == "poetry" assert result["version"] == "1.0.0" assert result["license-expression"] == "MIT" assert "repository" in result["urls"] assert result["requires-python"] == ">=2.7,<4.0,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" assert 'cleo<1.0.0,>=0.7.6; python_version ~= "2.7"' in result["dependencies"] assert ( 'cachecontrol[filecache]<1.0.0,>=0.12.4; python_version >= "3.4" ' 'and python_version < "4.0"' in result["dependencies"] ) assert "babel==2.9.0" in result["dependencies"] assert "mysql" in result["optional-dependencies"] assert "psycopg2<3.0,>=2.7" in result["optional-dependencies"]["pgsql"] assert len(settings["dev-dependencies"]["dev"]) == 2 assert result["scripts"] == {"poetry": "poetry.console:run"} assert result["entry-points"]["blogtool.parsers"] == { ".rst": "some_module:SomeClass" } assert settings["includes"] == ["lib/my_package", "tests", "CHANGELOG.md"] assert settings["excludes"] == ["my_package/excluded.py"]
def convert(project, filename, options): with open(filename, encoding="utf-8") as fp, cd( os.path.dirname(os.path.abspath(filename))): converter = PoetryMetaConverter( toml.load(fp)["tool"]["poetry"], project.core.ui if project else None) return converter.convert()
def get_dependencies(self, section: Optional[str] = None ) -> Dict[str, Requirement]: metadata = self.meta optional_dependencies = metadata.get("optional-dependencies", {}) dev_dependencies = self.tool_settings.get("dev-dependencies", {}) if section in (None, "default"): deps = metadata.get("dependencies", []) else: if section in optional_dependencies and section in dev_dependencies: self.core.ui.echo( f"The {section} section exists in both [optional-dependencies] " "and [dev-dependencies], the former is taken.", err=True, fg="yellow", ) if section in optional_dependencies: deps = optional_dependencies[section] elif section in dev_dependencies: deps = dev_dependencies[section] else: raise PdmUsageError(f"Non-exist section {section}") result = {} with cd(self.root): for line in deps: if line.startswith("-e "): req = parse_requirement(line[3:].strip(), True) else: req = parse_requirement(line) # make editable packages behind normal ones to override correctly. result[req.identify()] = req return result
def test_run_expand_env_vars(project, invoke, capfd): (project.root / "test_script.py").write_text("import os; print(os.getenv('FOO'))") project.tool_settings["scripts"] = { "test_cmd": 'python -c "foo, bar = 0, 1;print($FOO)"', "test_cmd_no_expand": "python -c 'print($FOO)'", "test_script": "python test_script.py", "test_cmd_array": ["python", "test_script.py"], "test_shell": { "shell": "echo $FOO" }, } project.write_pyproject() capfd.readouterr() with cd(project.root), temp_environ(): os.environ["FOO"] = "bar" invoke(["run", "test_cmd"], obj=project) assert capfd.readouterr()[0].strip() == "1" result = invoke(["run", "test_cmd_no_expand"], obj=project) assert result.exit_code == 1 invoke(["run", "test_script"], obj=project) assert capfd.readouterr()[0].strip() == "bar" invoke(["run", "test_cmd_array"], obj=project) assert capfd.readouterr()[0].strip() == "bar" invoke(["run", "test_shell"], obj=project) assert capfd.readouterr()[0].strip() == "bar"
def test_run_script_override_global_env(project, invoke, capfd): (project.root / "test_script.py").write_text("import os; print(os.getenv('FOO'))") project.tool_settings["scripts"] = { "_": { "env": { "FOO": "bar" } }, "test_env": { "cmd": "python test_script.py" }, "test_env_override": { "cmd": "python test_script.py", "env": { "FOO": "foobar" } }, } project.write_pyproject() capfd.readouterr() with cd(project.root): invoke(["run", "test_env"], obj=project) assert capfd.readouterr()[0].strip() == "bar" invoke(["run", "test_env_override"], obj=project) assert capfd.readouterr()[0].strip() == "foobar"
def test_search_package(invoke, tmp_path): with cd(tmp_path): result = invoke(["search", "requests"]) assert result.exit_code == 0 assert len(result.output.splitlines()) > 0 assert not tmp_path.joinpath("__pypackages__").exists() assert not tmp_path.joinpath(".pdm.toml").exists()
def test_run_call_script(project, invoke): (project.root / "test_script.py").write_text( textwrap.dedent(""" import argparse import sys def main(argv=None): parser = argparse.ArgumentParser() parser.add_argument("-c", "--code", type=int) args = parser.parse_args(argv) sys.exit(args.code) """)) project.tool_settings["scripts"] = { "test_script": { "call": "test_script:main" }, "test_script_with_args": { "call": "test_script:main(['-c', '9'])" }, } project.write_pyproject() with cd(project.root): result = invoke(["run", "test_script", "-c", "8"], obj=project) assert result.exit_code == 8 result = invoke(["run", "test_script_with_args"], obj=project) assert result.exit_code == 9
def test_export_replace_project_root(project): artifact = FIXTURES / "artifacts/first-2.0.2-py2.py3-none-any.whl" shutil.copy2(artifact, project.root) with cd(project.root): req = parse_requirement(f"./{artifact.name}") result = requirements.export(project, [req], Namespace(hashes=False)) assert "${PROJECT_ROOT}" not in result
def test_auto_global_project(tmp_path, core): tmp_path.joinpath(".pdm-home").mkdir() (tmp_path / ".pdm-home/config.toml").write_text( "[global_project]\nfallback = true\n" ) with cd(tmp_path): project = core.create_project(global_config=tmp_path / ".pdm-home/config.toml") assert project.is_global
def convert(project: Optional[Project], filename: PathLike, options: Optional[Namespace]) -> Tuple[Mapping, Mapping]: with open(filename, encoding="utf-8") as fp, cd( os.path.dirname(os.path.abspath(filename))): converter = FlitMetaConverter( toml.load(fp)["tool"]["flit"], project.core.ui if project else None) return converter.convert()
def test_run_script_with_dotenv_file(project, invoke, capfd): (project.root / "test_script.py").write_text("import os; print(os.getenv('FOO'))") project.tool_settings["scripts"] = { "test_script": {"cmd": "python test_script.py", "env_file": ".env"} } project.write_pyproject() (project.root / ".env").write_text("FOO=bar") capfd.readouterr() with cd(project.root): invoke(["run", "test_script"], obj=project) assert capfd.readouterr()[0].strip() == "bar"
def convert( project: Project | None, filename: str | Path, options: Namespace | None, ) -> tuple[Mapping[str, Any], Mapping[str, Any]]: with open(filename, "rb") as fp, cd(os.path.dirname(os.path.abspath(filename))): converter = PoetryMetaConverter( tomli.load(fp)["tool"]["poetry"], project.core.ui if project else None) return converter.convert()
def test_run_shell_script(project, invoke): project.tool_settings["scripts"] = { "test_script": { "shell": "echo hello > output.txt" } } project.write_pyproject() with cd(project.root): result = invoke(["run", "test_script"], obj=project) assert result.exit_code == 0 assert (project.root / "output.txt").read_text().strip() == "hello"
def invoke(isolated, monkeypatch): runner = CliRunner(mix_stderr=False) core = Core() invoker = functools.partial(runner.invoke, core, prog_name="pdm") monkeypatch.delenv("PDM_IGNORE_SAVED_PYTHON", raising=False) with cd(isolated): invoker(["config", "venv.location", str(isolated / "venvs")]) invoker(["config", "venv.backend", os.getenv("VENV_BACKEND", "virtualenv")]) if (isolated / ".pdm.toml").exists(): (isolated / ".pdm.toml").unlink() yield invoker
def test_project_with_combined_extras(fixture_project): project = fixture_project("demo-combined-extras") (project.root / "build").mkdir(exist_ok=True) with cd(project.root.as_posix()): wheel_name = build_wheel(str(project.root / "build")) wheel = distlib.wheel.Wheel(str(project.root / "build" / wheel_name)) all_requires = filter_requirements_with_extras(wheel.metadata.run_requires, ("all", )) for dep in ("urllib3", "chardet", "idna"): assert dep in all_requires
def convert( project: Optional[Project], filename: PathLike, options: Optional[Namespace], ) -> Tuple[Dict[str, Any], Dict]: with open(filename, encoding="utf-8") as fp, cd( os.path.dirname(os.path.abspath(filename)) ): converter = PoetryMetaConverter( toml.load(fp)["tool"]["poetry"], project.core.ui if project else None ) return converter.convert()
def test_run_with_patched_sysconfig(project, invoke, capfd): project.root.joinpath("script.py").write_text("""\ import sysconfig import json print(json.dumps(sysconfig.get_paths())) """) capfd.readouterr() with cd(project.root): result = invoke(["run", "python", "script.py"], obj=project) assert result.exit_code == 0 out = json.loads(capfd.readouterr()[0]) assert "__pypackages__" in out["purelib"]
def test_run_script_with_extra_args(project, invoke, capfd): (project.root / "test_script.py").write_text( textwrap.dedent(""" import sys print(*sys.argv[1:], sep='\\n') """)) project.tool_settings["scripts"] = {"test_script": "python test_script.py"} project.write_pyproject() with cd(project.root): invoke(["run", "test_script", "-a", "-b", "-c"], obj=project) out, _ = capfd.readouterr() assert out.splitlines()[-3:] == ["-a", "-b", "-c"]
def test_run_with_another_project_root(project, local_finder, invoke, capfd): project.meta["requires-python"] = ">=3.6" project.write_pyproject() invoke(["add", "first"], obj=project) with TemporaryDirectory(prefix="pytest-run-") as tmp_dir: Path(tmp_dir).joinpath("main.py").write_text( "import first;print(first.first([0, False, 1, 2]))\n") capfd.readouterr() with cd(tmp_dir): ret = invoke(["run", "-p", str(project.root), "python", "main.py"]) assert ret.exit_code == 0 out, _ = capfd.readouterr() assert out.strip() == "1"
def test_run_with_another_project_root(project, invoke, capfd): project.meta["requires-python"] = ">=3.6" project.write_pyproject() invoke(["add", "requests==2.24.0"], obj=project) with TemporaryDirectory(prefix="pytest-run-") as tmp_dir: Path(tmp_dir).joinpath("main.py").write_text( "import requests\nprint(requests.__version__)\n") capfd.readouterr() with cd(tmp_dir): ret = invoke(["run", "-p", str(project.root), "python", "main.py"]) assert ret.exit_code == 0 out, _ = capfd.readouterr() assert out.strip() == "2.24.0"
def dev_dependencies(self) -> Dict[str, Requirement]: """All development dependencies""" dev_group = self.tool_settings.get("dev-dependencies", {}) if not dev_group: return {} result = {} with cd(self.root): for _, deps in dev_group.items(): for line in deps: if line.startswith("-e "): req = parse_requirement(line[3:].strip(), True) else: req = parse_requirement(line) result[req.identify()] = req return result
def test_basic_integration(python_version, project_no_init, strict_invoke): """An e2e test case to ensure PDM works on all supported Python versions""" project = project_no_init project.root.joinpath("foo.py").write_text("import django\n") strict_invoke(["init"], input="\ny\n\n\n\n\n\n>=2.7\n", obj=project) strict_invoke(["use", "-f", python_version], obj=project) project._environment = None strict_invoke(["add", "django"], obj=project) with cd(project.root): strict_invoke(["run", "python", "foo.py"], obj=project) if python_version != "2.7": strict_invoke(["build", "-v"], obj=project) strict_invoke(["remove", "django"], obj=project) result = strict_invoke(["list"], obj=project) assert not any(line.strip().lower().startswith("django") for line in result.output.splitlines())
def install_editable(self, ireq: shims.InstallRequirement) -> None: setup_path = ireq.setup_py_path paths = self.environment.get_paths() install_script = importlib.import_module( "pdm._editable_install" ).__file__.rstrip("co") install_args = [ self.environment.python_executable, "-u", install_script, setup_path, paths["prefix"], paths["purelib"], paths["scripts"], ] with self.environment.activate(True), cd(ireq.unpacked_source_directory): result = subprocess.run(install_args, capture_output=True, check=True) stream.logger.debug(result.stdout.decode("utf-8"))
def get_dependencies(self, section: Optional[str] = None) -> Dict[str, Requirement]: metadata = self.meta if section in (None, "default"): deps = metadata.get("dependencies", []) elif section == "dev": deps = metadata.get("dev-dependencies", []) else: deps = metadata.get("optional-dependencies", {}).get(section, []) result = {} with cd(self.root): for line in deps: if line.startswith("-e "): req = parse_requirement(line[3:].strip(), True) else: req = parse_requirement(line) req.from_section = section or "default" # make editable packages behind normal ones to override correctly. result[req.identify()] = req return result
def test_import_another_sitecustomize(project, invoke, capfd): project.meta["requires-python"] = ">=2.7" project.write_pyproject() # a script for checking another sitecustomize is imported project.root.joinpath("foo.py").write_text( "import os;print(os.getenv('FOO'))") # ensure there have at least one sitecustomize can be imported # there may have more than one sitecustomize.py in sys.path project.root.joinpath("sitecustomize.py").write_text( "import os;os.environ['FOO'] = 'foo'") env = os.environ.copy() paths = env.get("PYTHONPATH") this_path = str(project.root) new_paths = [this_path] if not paths else [this_path, paths] env["PYTHONPATH"] = os.pathsep.join(new_paths) project._environment = None capfd.readouterr() with cd(project.root): result = invoke(["run", "python", "foo.py"], env=env) assert result.exit_code == 0, result.stderr out, _ = capfd.readouterr() assert out.strip() == "foo"
def test_basic_integration(python_version, core, tmp_path, invoke): """An e2e test case to ensure PDM works on all supported Python versions""" project = core.create_project(tmp_path) project.root.joinpath("foo.py").write_text("import django\n") additional_args = ["--no-self"] if python_version == "2.7" else [] invoke(["use", "-f", python_version], obj=project, strict=True) invoke(["init", "-n"], obj=project, strict=True) project.meta["name"] = "test-project" project.meta[ "requires-python"] = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" project.write_pyproject() project._environment = None invoke(["add", "django", "-v"] + additional_args, obj=project, strict=True) with cd(project.root): invoke(["run", "python", "foo.py"], obj=project, strict=True) if python_version != "2.7": invoke(["build", "-v"], obj=project, strict=True) invoke(["remove", "-v", "django"] + additional_args, obj=project, strict=True) result = invoke(["list"], obj=project, strict=True) assert not any(line.strip().lower().startswith("django") for line in result.output.splitlines())
def test_info_global_project(invoke, tmp_path): with cd(tmp_path): result = invoke(["info", "-g", "--where"]) assert "global-project" in result.output.strip()
def convert_package_paths(self) -> Dict[str, Union[List, Dict]]: """Return a {package_dir, packages, package_data, exclude_package_data} dict.""" package_dir = {} packages = [] py_modules = [] package_data = {"": ["*"]} exclude_package_data = {} with cd(self.project.root.as_posix()): if not self.includes: if os.path.isdir("src"): package_dir[""] = "src" packages = setuptools.find_packages("src") else: packages = setuptools.find_packages( exclude=["tests", "tests.*"]) if not packages: py_modules = [path[:-3] for path in glob.glob("*.py")] else: packages_set = set() includes = self.includes for include in includes[:]: if include.replace("\\", "/").endswith("/*"): include = include[:-2] if "*" not in include and os.path.isdir(include): include = include.rstrip("/\\") temp = setuptools.find_packages(include) if os.path.exists(include + "/__init__.py"): temp = [include ] + [f"{include}.{part}" for part in temp] elif temp: package_dir[""] = include packages_set.update(temp) includes.remove(include) packages[:] = list(packages_set) for include in includes: for path in glob.glob(include): if "/" not in path.lstrip("./") and path.endswith( ".py"): # Only include top level py modules py_modules.append(path.lstrip("./")[:-3]) if include.endswith(".py"): continue for package in packages: relpath = os.path.relpath(include, package) if not relpath.startswith(".."): package_data.setdefault(package, []).append(relpath) for exclude in self.excludes or []: for package in packages: relpath = os.path.relpath(exclude, package) if not relpath.startswith(".."): exclude_package_data.setdefault(package, []).append(relpath) if packages and py_modules: raise ProjectError( "Can't specify packages and py_modules at the same time.") return { "package_dir": package_dir, "packages": packages, "py_modules": py_modules, "package_data": package_data, "exclude_package_data": exclude_package_data, }
def convert(project, filename): with open(filename, encoding="utf-8") as fp, cd( os.path.dirname(os.path.abspath(filename))): converter = PoetryMetaConverter(toml.load(fp)["tool"]["poetry"]) return dict(converter), converter.settings