Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scripts run package functions #5390

Merged
merged 4 commits into from
Oct 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/5294.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ability for callable scripts in Pipfile under [scripts]. Callables can now be added like: ``<pathed.module>:<func>`` and can also take arguments. For exaple: ``func = {call = "package.module:func('arg1', 'arg2')"}`` then this can be activated in the shell with ``pipenv run func``
32 changes: 31 additions & 1 deletion pipenv/cmdparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,61 @@
import re
import shlex

from pipenv.vendor import tomlkit


class ScriptEmptyError(ValueError):
pass


class ScriptParseError(ValueError):
pass


def _quote_if_contains(value, pattern):
if next(iter(re.finditer(pattern, value)), None):
return '"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', value))
return value


def _parse_toml_inline_table(value: tomlkit.items.InlineTable) -> str:
"""parses the [scripts] in pipfile and converts: `{call = "package.module:func('arg')"}` into an executable command"""
keys_list = list(value.keys())
if len(keys_list) > 1:
raise ScriptParseError("More than 1 key in toml script line")
cmd_key = keys_list[0]
if cmd_key not in Script.script_types:
raise ScriptParseError(
f"Not an accepted script callabale, options are: {Script.script_types}"
)
if cmd_key == "call":
module, _, func = str(value["call"]).partition(":")
if not module or not func:
raise ScriptParseError("Callable must be like: <pathed.module>:<func>")
if re.search(r"\(.*?\)", func) is None:
func += "()"
return f'python -c "import {module} as _m; _m.{func}"'


class Script(object):
"""Parse a script line (in Pipfile's [scripts] section).

This always works in POSIX mode, even on Windows.
"""

script_types = ["call"]

def __init__(self, command, args=None):
self._parts = [command]
if args:
self._parts.extend(args)

@classmethod
def parse(cls, value):
if isinstance(value, str):
if isinstance(value, tomlkit.items.InlineTable):
cmd_string = _parse_toml_inline_table(value)
value = shlex.split(cmd_string)
elif isinstance(value, str):
value = shlex.split(value)
if not value:
raise ScriptEmptyError(value)
Expand Down
31 changes: 31 additions & 0 deletions tests/integration/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pipenv.project import Project
from pipenv.utils.shell import subprocess_run, temp_environ
from pipenv.utils.shell import mkdir_p


@pytest.mark.run
Expand Down Expand Up @@ -63,6 +64,36 @@ def test_scripts(pipenv_instance_pypi):
assert c.stdout.strip() == "WORLD"


@pytest.mark.run
def test_scripts_with_package_functions(pipenv_instance_pypi):
with pipenv_instance_pypi(chdir=True) as p:
p.pipenv('install')
pkg_path = os.path.join(p.path, "pkg")
mkdir_p(pkg_path)
file_path = os.path.join(pkg_path, "mod.py")
with open(file_path, "w+") as f:
f.write("""
def test_func():
print("success")

def arg_func(s, i):
print(f"{s.upper()}. Easy as {i}")
""")

with open(p.pipfile_path, 'w') as f:
f.write(r"""
[scripts]
pkgfunc = {call = "pkg.mod:test_func"}
argfunc = {call = "pkg.mod:arg_func('abc', 123)"}
""")

c = p.pipenv('run pkgfunc')
assert c.stdout.strip() == "success"

c = p.pipenv('run argfunc')
assert c.stdout.strip() == "ABC. Easy as 123"


@pytest.mark.run
@pytest.mark.skip_windows
def test_run_with_usr_env_shebang(pipenv_instance_pypi):
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_vendor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import pytest
import pytz
import tomlkit
from pipenv.vendor import tomlkit


@pytest.mark.parametrize('dt, content', [
Expand Down