未验证 提交 f8f93cea 编写于 作者: F Frost Ming 提交者: GitHub

Merge pull request #179 from frostming/feature/dot-env

Support dotenv file loading
......@@ -251,6 +251,18 @@ start_server.env = {FOO = "bar", FLASK_ENV = "development"}
Note how we use [TOML's syntax](https://github.com/toml-lang/toml) to define a compound dictionary.
A dotenv file is also supported via `env_file = "<file_path>"` setting.
For environment variables and/or dotenv file shared by all scripts, you can define `env` and `env_file`
settings under a special key named `_` of `tool.pdm.scripts` table:
```toml
[tool.pdm.scripts]
_.env_file = ".env"
start_server = "flask run -p 54321"
migrate_db = "flask db upgrade"
```
### Show the list of scripts shortcuts
Use `pdm run --list/-l` to show the list of available script shortcuts:
......
Improve the user experience of `pdm run`:
- Add a special key in tool.pdm.scripts that holds configurations shared by all scripts.
- Support loading env var from a dot-env file.
......@@ -400,6 +400,12 @@ summary = "Sexy fonts for the console"
[package.dependencies]
colorama = "*"
[[package]]
name = "python-dotenv"
sections = ["default"]
version = "0.15.0"
summary = "Add .env support to your django/flask apps in development and deployments"
[[package]]
name = "pythonfinder"
sections = ["default"]
......@@ -775,6 +781,10 @@ summary = "Backport of pathlib-compatible object wrapper for zip files"
{file = "python_cfonts-1.3.1-py2.py3-none-any.whl", hash = "sha256:8329e38f393f7e9cbd8c1c905c9fd8645bf0837348114a281639d619e0a0f4e2"},
{file = "python-cfonts-1.3.1.tar.gz", hash = "sha256:50d9e3153034d7e27e74c3c62009275ff0fe0d54d8cd3d283e7f014a9d830d43"},
]
"python-dotenv 0.15.0" = [
{file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"},
{file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"},
]
"pythonfinder 1.2.5" = [
{file = "pythonfinder-1.2.5-py2.py3-none-any.whl", hash = "sha256:b1bd693bcd6fb142d85d395ec2dad5e1791fb16b0fe29616b9395c8c72a69752"},
{file = "pythonfinder-1.2.5.tar.gz", hash = "sha256:481fba9cb7ffa43fe5b5b5c4c5cbcec565a79762e24daff65043158a93fc1986"},
......@@ -931,4 +941,5 @@ summary = "Backport of pathlib-compatible object wrapper for zip files"
[root]
meta_version = "0.0.1"
content_hash = "md5:5307e1dc97bc1df4e9488bc372f767a8"
content_hash = "md5:320ab920bc7cee68a94fd078fda5541f"
......@@ -5,7 +5,7 @@ import re
import shlex
import subprocess
import sys
from typing import List, Union
from typing import Dict, List, Optional, Union
from pdm.cli.commands.base import BaseCommand
from pdm.exceptions import PdmUsageError
......@@ -17,7 +17,7 @@ from pdm.utils import find_project_root
class Command(BaseCommand):
"""Run commands or scripts with local packages loaded"""
OPTIONS = ["env", "help"]
OPTIONS = ["env", "env_file", "help"]
TYPES = ["cmd", "shell", "call"]
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
......@@ -38,10 +38,18 @@ class Command(BaseCommand):
def _run_command(
project: Project,
args: Union[List[str], str],
shell=False,
env=None,
shell: bool = False,
env: Optional[Dict[str, str]] = None,
env_file: Optional[str] = None,
) -> None:
with project.environment.activate():
if env_file:
import dotenv
stream.echo(f"Loading .env file: {stream.green(env_file)}", err=True)
dotenv.load_dotenv(
project.root.joinpath(env_file).as_posix(), override=True
)
if env:
os.environ.update(env)
if shell:
......@@ -90,7 +98,13 @@ class Command(BaseCommand):
)
return kind, value, options
def _run_script(self, project: Project, script_name: str, args: List[str]) -> None:
def _run_script(
self,
project: Project,
script_name: str,
args: List[str],
global_env_options: Dict[str, Union[str, Dict[str, str]]],
) -> None:
script = project.scripts[script_name]
kind, value, options = self._normalize_script(script)
if kind == "cmd":
......@@ -113,6 +127,11 @@ class Command(BaseCommand):
f"import sys, {module} as {short_name};"
f"sys.exit({short_name}.{func})",
] + args
if "env" in global_env_options:
options["env"] = {**global_env_options["env"], **options.get("env", {})}
options["env_file"] = options.get(
"env_file", global_env_options.get("env_file")
)
stream.echo(f"Running {kind} script: {stream.green(str(args))}", err=True)
return self._run_command(project, args, **options)
......@@ -129,14 +148,21 @@ class Command(BaseCommand):
def handle(self, project: Project, options: argparse.Namespace) -> None:
if options.list:
return self._show_list(project)
global_env_options = project.scripts.get("_", {}) if project.scripts else {}
if project.scripts and options.command in project.scripts:
self._run_script(project, options.command, options.args)
self._run_script(project, options.command, options.args, global_env_options)
elif os.path.isfile(options.command) and options.command.endswith(".py"):
# Allow executing py scripts like `pdm run my_script.py`.
# In this case, the nearest `__pypackages__` will be loaded as
# the library source.
new_root = find_project_root(os.path.abspath(options.command))
project = Project(new_root) if new_root else project
self._run_command(project, ["python", options.command] + options.args)
self._run_command(
project,
["python", options.command] + options.args,
**global_env_options,
)
else:
self._run_command(project, [options.command] + options.args)
self._run_command(
project, [options.command] + options.args, **global_env_options
)
......@@ -38,6 +38,7 @@ pdm-pep517 = "<1.0.0,>=0.1.0"
importlib-metadata = {version = "*", marker = "python_version<'3.8'"}
pep517 = "*"
pycomplete = "<1.0.0,>=0.2.0"
python-dotenv = "<1.0.0,>=0.15.0"
[tool.pdm.dev-dependencies]
pytest = "*"
......
......@@ -128,6 +128,35 @@ def test_run_script_with_env_defined(project, invoke, capfd):
assert capfd.readouterr()[0].strip() == "bar"
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 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_run_show_list_of_scripts(project, invoke):
project.tool_settings["scripts"] = {
"test_cmd": "flask db upgrade",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册