未验证 提交 89d61172 编写于 作者: F Frost Ming

change the mechanism to enable PEP 582

上级 47dd14f9
......@@ -108,7 +108,7 @@ if __name__ == '__main__':
app.run()
```
Set environment variable `export PDM_PYTHON_PEP582=1`. Now you can run the app directly with your familiar **Python interpreter**:
Set environment variable `eval $(pdm --pep582)`. Now you can run the app directly with your familiar **Python interpreter**:
```bash
$ python /home/frostming/workspace/flask_app/app.py
......
......@@ -46,11 +46,15 @@ $ pip install --user pdm
### Enable PEP 582 globally
To make the Python interpreters aware of PEP 582 packages, set the environment variable `PDM_PYTHON_PEP582` to `1`.
You may want to write a line in your `.bash_profile`(or similar profiles) to make it effective when login:
To make the Python interpreters aware of PEP 582 packages, one need to add the `pdm/pep582/sitecustomize.py`
to the Python library search path. The command can be produced by `pdm --pep582 [<SHELL>]` and if `<SHELL>`
isn't given, PDM will pick one based on some guesses.
You may want to write a line in your `.bash_profile`(or similar profiles) to make it effective when login.
For example, in bash you can do this:
```bash
export PDM_PYTHON_PEP582=1
$ pdm --pep582 >> ~/.bash_profile
```
**This setup may become the default in the future.**
......
......@@ -287,5 +287,5 @@ By default, system-level site-packages will be excluded from the `sys.path` when
## How we make PEP 582 packages available to the Python interpreter
Thanks to the [site packages loading](https://docs.python.org/3/library/site.html) on Python startup. It is possible to patch the `sys.path`
by placing a `_pdm_pep582.pth` together with a small script under the `site-packages` directory. The interpreter can search the directories
for the neareset `__pypackage__` folder and append it to the `sys.path` variable. This is totally done by PDM and users shouldn't be aware.
by executing the `sitecustomize.py` shipped with PDM. The interpreter can search the directories
for the neareset `__pypackage__` folder and append it to the `sys.path` variable.
Write a `sitecustomize.py` instead of a `.pth` file to enable PEP 582.
......@@ -457,6 +457,12 @@ sections = ["default", "dev"]
version = "50.3.2"
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
[[package]]
name = "shellingham"
sections = ["default"]
version = "1.3.2"
summary = "Tool to Detect Surrounding Shell"
[[package]]
name = "six"
sections = ["default", "dev", "doc"]
......@@ -859,6 +865,10 @@ summary = "Backport of pathlib-compatible object wrapper for zip files"
{file = "setuptools-50.3.2-py3-none-any.whl", hash = "sha256:2c242a0856fbad7efbe560df4a7add9324f340cf48df43651e9604924466794a"},
{file = "setuptools-50.3.2.zip", hash = "sha256:ed0519d27a243843b05d82a5e9d01b0b083d9934eaa3d02779a23da18077bd3c"},
]
"shellingham 1.3.2" = [
{file = "shellingham-1.3.2-py2.py3-none-any.whl", hash = "sha256:7f6206ae169dc1a03af8a138681b3f962ae61cc93ade84d0585cca3aaf770044"},
{file = "shellingham-1.3.2.tar.gz", hash = "sha256:576c1982bea0ba82fb46c36feb951319d7f42214a82634233f58b40d858a751e"},
]
"six 1.15.0" = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
......@@ -941,5 +951,5 @@ summary = "Backport of pathlib-compatible object wrapper for zip files"
[root]
meta_version = "0.0.1"
content_hash = "md5:320ab920bc7cee68a94fd078fda5541f"
content_hash = "md5:cf7dd9fb5ea311d0fb65a7e99ba86e71"
import json
import os
import shutil
import sys
from pathlib import Path
from typing import Dict, Iterable, List, Optional, Sequence
import click
import pythonfinder
import tomlkit
from pkg_resources import safe_name
from pdm.cli.utils import (
......@@ -28,6 +27,10 @@ from pdm.project import Project
from pdm.resolver import resolve
from pdm.utils import get_python_version
PEP582_PATH = os.path.join(
os.path.dirname(sys.modules[__name__.split(".")[0]].__file__), "pep582"
)
def do_lock(
project: Project,
......@@ -97,7 +100,6 @@ def do_sync(
candidates.update(project.get_locked_candidates())
handler = project.core.synchronizer_class(candidates, project.environment)
handler.synchronize(clean=clean, dry_run=dry_run)
project.environment.install_pep582_launcher()
def do_add(
......@@ -364,6 +366,8 @@ def do_init(
python_requires: str = "",
) -> None:
"""Bootstrap the project and create a pyproject.toml"""
import tomlkit
data = {
"tool": {
"pdm": {
......@@ -388,13 +392,14 @@ def do_init(
project._pyproject.setdefault("tool", {})["pdm"] = data["tool"]["pdm"]
project._pyproject["build-system"] = data["build-system"]
project.write_pyproject()
project.environment.install_pep582_launcher()
def do_use(project: Project, python: str, first: bool = False) -> None:
"""Use the specified python version and save in project config.
The python can be a version string or interpreter path.
"""
import pythonfinder
if python and not all(c.isdigit() for c in python.split(".")):
if Path(python).exists():
python_path = Path(python).absolute().as_posix()
......@@ -529,3 +534,29 @@ def ask_for_import(project: Project) -> None:
return
key, filepath = importable_files[int(choice)]
do_import(project, filepath, key)
def print_pep582_command(shell: str = "AUTO"):
"""Print the export PYTHONPATH line to be evaluated by the shell."""
import shellingham
if shell == "AUTO":
shell = shellingham.detect_shell()[0]
shell = shell.lower()
lib_path = PEP582_PATH.replace("'", "\\'")
if shell in ("zsh", "bash"):
result = f"export PYTHONPATH='{lib_path}':$PYTHONPATH"
elif shell == "fish":
result = f"set -x PYTHONPATH '{lib_path}' $PYTHONPATH"
elif shell == "powershell":
result = f'$env:PYTHONPATH="{lib_path};$env:PYTHONPATH"'
elif shell == "cmd":
result = f"set PYTHONPATH={lib_path};%PYTHONPATH%"
elif shell in ("tcsh", "csh"):
result = f"setenv PYTHONPATH '{lib_path}':$PYTHONPATH"
else:
raise PdmUsageError(
f"Unsupported shell: {shell}, please specify another shell "
"via `--pep582 <SHELL>`"
)
stream.echo(result)
......@@ -19,7 +19,8 @@ class Command(BaseCommand):
)
def handle(self, project: Project, options: argparse.Namespace) -> None:
import shellingham
from pycomplete import Completer
completer = Completer(project.core.parser)
stream.echo(completer.render(options.shell))
stream.echo(completer.render(options.shell or shellingham.detect_shell()[0]))
......@@ -7,6 +7,7 @@ import subprocess
import sys
from typing import Dict, List, Optional, Union
from pdm.cli.actions import PEP582_PATH
from pdm.cli.commands.base import BaseCommand
from pdm.exceptions import PdmUsageError
from pdm.iostream import stream
......@@ -47,7 +48,11 @@ class Command(BaseCommand):
env: Optional[Dict[str, str]] = None,
env_file: Optional[str] = None,
) -> None:
os.environ.update({"PDM_PYTHON_PEP582": "1"})
if "PYTHONPATH" in os.environ:
new_path = os.sep.join([PEP582_PATH, os.getenv("PYTHONPATH")])
else:
new_path = PEP582_PATH
os.environ.update({"PYTHONPATH": new_path})
if env_file:
import dotenv
......
......@@ -64,7 +64,16 @@ dry_run_option = Option(
"--dry-run",
action="store_true",
default=False,
help="Only prints actions without actually running them.",
help="Only prints actions without actually running them",
)
pep582_option = Option(
"--pep582",
const="AUTO",
metavar="SHELL",
nargs="?",
help="Print the command line to be eval'd by the shell",
)
sections_group = ArgumentGroup()
......
......@@ -9,8 +9,9 @@ import click
import pkg_resources
from resolvelib import Resolver
from pdm.cli.actions import print_pep582_command
from pdm.cli.commands.base import BaseCommand
from pdm.cli.options import verbose_option
from pdm.cli.options import pep582_option, verbose_option
from pdm.cli.utils import PdmFormatter, PdmParser
from pdm.installers import Synchronizer
from pdm.iostream import stream
......@@ -57,6 +58,7 @@ class Core:
help="show the version and exit",
)
verbose_option.add_to_parser(self.parser)
pep582_option.add_to_parser(self.parser)
self.subparsers = self.parser.add_subparsers()
for _, name, _ in pkgutil.iter_modules(COMMANDS_MODULE_PATH):
......@@ -85,6 +87,9 @@ class Core:
options.project = obj
if options.global_project:
options.project = options.global_project
if options.pep582:
print_pep582_command(options.pep582)
sys.exit(0)
if not getattr(options, "project", None):
options.project = self.project_class()
......
......@@ -2,7 +2,6 @@ from __future__ import annotations
import collections
import os
import pkgutil
import re
import shutil
import sys
......@@ -390,23 +389,6 @@ class Environment:
re.sub(rb"#!.+?python.*?$", shebang, child.read_bytes(), flags=re.M)
)
def install_pep582_launcher(self) -> None:
"""Install a PEP 582 launcher to the site packages path
of given Python interperter.
"""
lib_path = Path(get_sys_config_paths(self.python_executable)["purelib"])
if lib_path.joinpath("_pdm_pep582.pth").is_file():
stream.echo("PEP 582 launcher is ready.", verbosity=stream.DETAIL)
return
stream.echo("Installing PEP 582 launcher", verbosity=stream.DETAIL)
lib_path.joinpath("_pdm_pep582.py").write_bytes(
pkgutil.get_data(__name__, "../installers/_pep582.py")
)
lib_path.joinpath("_pdm_pep582.pth").write_text(
"import _pdm_pep582;_pdm_pep582.init()\n"
)
stream.echo("PEP 582 launcher is ready.", verbosity=stream.DETAIL)
class GlobalEnvironment(Environment):
"""Global environment"""
......@@ -427,6 +409,3 @@ class GlobalEnvironment(Environment):
@property
def packages_path(self) -> Optional[Path]:
return None
def install_pep582_launcher(self):
pass
......@@ -2,10 +2,6 @@ import os
import site
import sys
import warnings
from distutils.sysconfig import get_python_lib
# Global state to avoid recursive execution
_initialized = False
def get_pypackages_path(maxdepth=5):
......@@ -36,18 +32,16 @@ def get_pypackages_path(maxdepth=5):
return result
def init():
global _initialized
if (
os.getenv("PDM_PYTHON_PEP582", "").lower() not in ("true", "1", "yes")
or _initialized
):
# Do nothing if pep 582 is not enabled explicitly
return
_initialized = True
def main():
self_path = os.path.normcase(os.path.dirname(os.path.abspath(__file__)))
sys.path[:] = [path for path in sys.path if os.path.normcase(path) != self_path]
with_site_packages = os.getenv("PDM_WITH_SITE_PACKAGES")
needs_user_site = False
needs_site_packages = False
if sys.version_info[0] == 2 and getattr(sys, "argv", None) is None:
if getattr(sys, "argv", None) is None:
warnings.warn(
"PEP 582 can't be loaded based on the script path. "
"As Python 2.7 reached the end of life on 2020/01/01, "
......@@ -55,35 +49,35 @@ def init():
)
else:
script_path = sys.argv[0]
if os.path.exists(script_path) and os.path.normcase(
os.path.abspath(script_path)
).startswith(os.path.normcase(sys.prefix)):
with_site_packages = True
needs_user_site = os.path.normcase(script_path).startswith(
os.path.normcase(site.USER_BASE)
)
needs_site_packages = any(
os.path.normcase(script_path).startswith(os.path.normcase(p))
for p in site.PREFIXES
)
libpath = get_pypackages_path()
if not libpath:
return
if not with_site_packages:
# First, drop system-sites related paths.
original_sys_path = sys.path[:]
known_paths = set()
system_sites = {
os.path.normcase(site)
for site in (
get_python_lib(plat_specific=False),
get_python_lib(plat_specific=True),
)
}
for path in system_sites:
site.addsitedir(path, known_paths=known_paths)
system_paths = set(
os.path.normcase(path) for path in sys.path[len(original_sys_path) :]
)
original_sys_path = [
path
for path in original_sys_path
if os.path.normcase(path) not in system_paths
]
sys.path = original_sys_path
# First, drop site related paths.
original_sys_path = sys.path[:]
paths_to_remove = set()
if not (with_site_packages or needs_user_site):
site.addusersitepackages(paths_to_remove)
if not (with_site_packages or needs_site_packages):
site.addsitepackages(paths_to_remove)
paths_to_remove = set(os.path.normcase(path) for path in paths_to_remove)
original_sys_path = [
path
for path in original_sys_path
if os.path.normcase(path) not in paths_to_remove
]
sys.path[:] = original_sys_path
# Second, add lib directories, ensuring .pth file are processed.
site.addsitedir(libpath)
main()
del main
......@@ -39,6 +39,7 @@ importlib-metadata = {version = "*", marker = "python_version<'3.8'"}
pep517 = "*"
pycomplete = "<1.0.0,>=0.2.0"
python-dotenv = "<1.0.0,>=0.15.0"
shellingham = "<2.0.0,>=1.3.2"
[tool.pdm.dev-dependencies]
pytest = "*"
......
......@@ -5,6 +5,7 @@ import textwrap
import pytest
from pdm.cli.actions import PEP582_PATH
from pdm.utils import cd, temp_environ
......@@ -29,7 +30,7 @@ def test_pep582_launcher_for_python_interpreter(project, invoke):
)
invoke(["add", "requests==2.24.0"], obj=project)
env = os.environ.copy()
env.update({"PDM_PYTHON_PEP582": "1"})
env.update({"PYTHONPATH": PEP582_PATH})
output = subprocess.check_output(
[project.environment.python_executable, str(project.root.joinpath("main.py"))],
env=env,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册