File: test_project.py

package info (click to toggle)
pdm 2.2.1%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 2,140 kB
  • sloc: python: 18,313; makefile: 11
file content (270 lines) | stat: -rw-r--r-- 9,213 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
import os
import sys
import venv
from pathlib import Path

import pytest
from packaging.version import parse

from pdm.cli.commands.venv.utils import get_venv_python
from pdm.utils import cd


def test_project_python_with_pyenv_support(project, mocker, monkeypatch):

    del project.project_config["python.path"]
    project._python = None
    monkeypatch.setenv("PDM_IGNORE_SAVED_PYTHON", "1")
    mocker.patch("pdm.project.core.PYENV_ROOT", str(project.root))
    pyenv_python = project.root / "shims/python"
    if os.name == "nt":
        pyenv_python = pyenv_python.with_suffix(".bat")
    pyenv_python.parent.mkdir()
    pyenv_python.touch()
    mocker.patch(
        "findpython.python.PythonVersion._get_version",
        return_value=parse("3.8.0"),
    )
    mocker.patch(
        "findpython.python.PythonVersion._get_interpreter", return_value=sys.executable
    )
    assert Path(project.python.path) == pyenv_python
    assert project.python.executable == Path(sys.executable)

    # Clean cache
    project._python = None

    project.project_config["python.use_pyenv"] = False
    assert Path(project.python.path) != pyenv_python


def test_project_config_items(project):
    config = project.config

    for item in ("python.use_pyenv", "pypi.url", "cache_dir"):
        assert item in config


def test_project_config_set_invalid_key(project):
    config = project.project_config

    with pytest.raises(KeyError):
        config["foo"] = "bar"


def test_project_sources_overriding(project):
    project.project_config["pypi.url"] = "https://test.pypi.org/simple"
    assert project.sources[0]["url"] == "https://test.pypi.org/simple"

    project.tool_settings["source"] = [
        {"url": "https://example.org/simple", "name": "pypi", "verify_ssl": True}
    ]
    assert project.sources[0]["url"] == "https://example.org/simple"


def test_project_sources_env_var_expansion(project, monkeypatch):
    monkeypatch.setenv("PYPI_USER", "user")
    monkeypatch.setenv("PYPI_PASS", "password")
    project.project_config[
        "pypi.url"
    ] = "https://${PYPI_USER}:${PYPI_PASS}@test.pypi.org/simple"
    # expanded in sources
    assert project.sources[0]["url"] == "https://user:password@test.pypi.org/simple"
    # not expanded in project config
    assert (
        project.project_config["pypi.url"]
        == "https://${PYPI_USER}:${PYPI_PASS}@test.pypi.org/simple"
    )

    project.tool_settings["source"] = [
        {
            "url": "https://${PYPI_USER}:${PYPI_PASS}@example.org/simple",
            "name": "pypi",
            "verify_ssl": True,
        }
    ]
    # expanded in sources
    assert project.sources[0]["url"] == "https://user:password@example.org/simple"
    # not expanded in tool settings
    assert (
        project.tool_settings["source"][0]["url"]
        == "https://${PYPI_USER}:${PYPI_PASS}@example.org/simple"
    )

    project.tool_settings["source"] = [
        {
            "url": "https://${PYPI_USER}:${PYPI_PASS}@example2.org/simple",
            "name": "example2",
            "verify_ssl": True,
        }
    ]
    # expanded in sources
    assert project.sources[1]["url"] == "https://user:password@example2.org/simple"
    # not expanded in tool settings
    assert (
        project.tool_settings["source"][0]["url"]
        == "https://${PYPI_USER}:${PYPI_PASS}@example2.org/simple"
    )


def test_global_project(tmp_path, core):
    project = core.create_project(tmp_path, True)
    assert project.environment.is_global


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 test_project_use_venv(project):
    del project.project_config["python.path"]
    project._python = None
    scripts = "Scripts" if os.name == "nt" else "bin"
    suffix = ".exe" if os.name == "nt" else ""
    venv.create(project.root / "venv")

    project.project_config["python.use_venv"] = True
    env = project.environment
    assert (
        env.interpreter.executable
        == project.root / "venv" / scripts / f"python{suffix}"
    )
    assert env.is_global


def test_project_packages_path(project):
    packages_path = project.environment.packages_path
    version = ".".join(map(str, sys.version_info[:2]))
    if os.name == "nt" and sys.maxsize <= 2**32:
        assert packages_path.name == version + "-32"
    else:
        assert packages_path.name == version


def test_project_auto_detect_venv(project):

    venv.create(project.root / "test_venv")

    scripts = "Scripts" if os.name == "nt" else "bin"
    suffix = ".exe" if os.name == "nt" else ""

    project.project_config["python.use_venv"] = True
    project._python = None
    project.project_config["python.path"] = (
        project.root / "test_venv" / scripts / f"python{suffix}"
    ).as_posix()

    assert project.environment.is_global


@pytest.mark.path
def test_ignore_saved_python(project, monkeypatch):
    project.project_config["python.use_venv"] = True
    project._python = None
    scripts = "Scripts" if os.name == "nt" else "bin"
    suffix = ".exe" if os.name == "nt" else ""
    venv.create(project.root / "venv")
    monkeypatch.setenv("PDM_IGNORE_SAVED_PYTHON", "1")
    assert project.python.executable != project.project_config["python.path"]
    assert (
        project.python.executable == project.root / "venv" / scripts / f"python{suffix}"
    )


def test_select_dependencies(project):
    project.meta["dependencies"] = ["requests"]
    project.meta["optional-dependencies"] = {
        "security": ["cryptography"],
        "venv": ["virtualenv"],
    }
    project.tool_settings["dev-dependencies"] = {"test": ["pytest"], "doc": ["mkdocs"]}
    assert sorted(project.get_dependencies()) == ["requests"]
    assert sorted(project.dependencies) == ["requests"]

    assert sorted(project.get_dependencies("security")) == ["cryptography"]
    assert sorted(project.get_dependencies("test")) == ["pytest"]
    assert sorted(project.dev_dependencies) == ["mkdocs", "pytest"]

    assert sorted(project.iter_groups()) == [
        "default",
        "doc",
        "security",
        "test",
        "venv",
    ]


def test_global_python_path_config(project_no_init, tmp_path):
    tmp_path.joinpath(".pdm.toml").unlink()
    project_no_init.global_config["python.path"] = sys.executable
    # Recreate the project to clean cached properties
    p = project_no_init.core.create_project(
        project_no_init.root, global_config=tmp_path / ".pdm-home/config.toml"
    )
    assert p.python.executable == Path(sys.executable)
    assert "python.path" not in p.project_config


@pytest.mark.path
def test_set_non_exist_python_path(project_no_init):
    project_no_init.project_config["python.path"] = "non-exist-python"
    project_no_init._python = None
    assert project_no_init.python.executable.name != "non-exist-python"


@pytest.mark.usefixtures("venv_backends")
def test_create_venv_first_time(invoke, project, local_finder):
    project.project_config.update({"venv.in_project": False})
    del project.project_config["python.path"]
    result = invoke(["install"], obj=project)
    assert result.exit_code == 0
    venv_parent = project.root / "venvs"
    venv_path = next(venv_parent.iterdir(), None)
    assert venv_path is not None

    assert Path(project.project_config["python.path"]).relative_to(venv_path)


@pytest.mark.usefixtures("venv_backends", "local_finder")
@pytest.mark.parametrize("with_pip", [True, False])
def test_create_venv_in_project(invoke, project, with_pip):
    project.project_config.update({"venv.in_project": True, "venv.with_pip": with_pip})
    del project.project_config["python.path"]
    result = invoke(["install"], obj=project)
    assert result.exit_code == 0
    assert project.root.joinpath(".venv").exists()
    working_set = project.environment.get_working_set()
    assert ("pip" in working_set) is with_pip


@pytest.mark.usefixtures("venv_backends")
def test_find_interpreters_from_venv(invoke, project, local_finder):
    project.project_config.update({"venv.in_project": False})
    del project.project_config["python.path"]
    result = invoke(["install"], obj=project)
    assert result.exit_code == 0
    venv_parent = project.root / "venvs"
    venv_path = next(venv_parent.iterdir(), None)
    venv_python = get_venv_python(venv_path)

    assert any(venv_python == p.executable for p in project.find_interpreters())


def test_iter_project_venvs(project):
    from pdm.cli.commands.venv import utils

    venv_parent = Path(project.config["venv.location"])
    venv_prefix = utils.get_venv_prefix(project)
    for name in ("foo", "bar", "baz"):
        venv_parent.joinpath(venv_prefix + name).mkdir(parents=True)
    dot_venv_python = utils.get_venv_python(project.root / ".venv")
    dot_venv_python.parent.mkdir(parents=True)
    dot_venv_python.touch()
    venv_keys = [key for key, _ in utils.iter_venvs(project)]
    assert sorted(venv_keys) == ["bar", "baz", "foo", "in-project"]