feat: add viv.run() to accompany inline metatdata approach

This commit is contained in:
Daylin Morgan 2024-01-03 13:49:27 -06:00
parent f21b90d962
commit f53d00e8e2
Signed by: daylin
GPG key ID: C1E52E7DD81DF79F
3 changed files with 67 additions and 9 deletions

17
examples/pep723_run.py Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
__import__("viv").run()
import requests
from rich import print
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
print([(k, v["title"]) for k, v in data.items()][:10])

View file

@ -1 +1 @@
from .viv import __version__, use, main # noqa from .viv import __version__, use, main, run # noqa

View file

@ -2775,6 +2775,16 @@ def use(*packages: str, track_exe: bool = False, name: str = "") -> Path:
return vivenv.path return vivenv.path
def run() -> Path:
"""create a vivenv and append to sys.path using embedded metadata"""
source = get_caller_path().read_text()
metadata = _read_metadata_block(source)
deps = metadata.get("dependencies", [])
if requires := metadata.get("requires-python", ""):
_check_python(requires)
return use(*deps)
def combined_spec(reqs: List[str], requirements: Path) -> List[str]: def combined_spec(reqs: List[str], requirements: Path) -> List[str]:
if requirements: if requirements:
with requirements.open("r") as f: with requirements.open("r") as f:
@ -2854,16 +2864,36 @@ def make_executable(path: Path) -> None:
os.chmod(path, mode) os.chmod(path, mode)
def uses_viv(txt: str) -> bool: # TODO: abstract/deduplicate these functions
def _uses_viv_use(txt: str) -> bool:
return bool( return bool(
re.search( re.search(
r""" r"""
^(?!\#)\s* ^(?!\#)\s*
(?:__import__\(\s*["']viv["']\s*\)) (?:__import__\(\s*["']viv["']\s*\).use)
| |
(?:from\ viv\ import\ use) (?:from\ viv\ import\ use)
#|
#(?:import\ viv)
|
(?:viv.use)
""",
txt,
re.VERBOSE | re.MULTILINE,
)
)
def _uses_viv_run(txt: str) -> bool:
return bool(
re.search(
r"""
^(?!\#)\s*
(?:__import__\(\s*["']viv["']\s*\).run)
| |
(?:import\ viv) (?:from\ viv\ import\ run)
|
(?:viv.run)
""", """,
txt, txt,
re.VERBOSE | re.MULTILINE, re.VERBOSE | re.MULTILINE,
@ -3405,21 +3435,23 @@ class Viv:
else: else:
scriptpath = tmppath / name scriptpath = tmppath / name
script_text = fetch_script(script) script_text = fetch_script(script)
scriptpath.write_text(script_text)
viv_used = uses_viv(script_text) has_use = _uses_viv_use(script_text)
has_run = _uses_viv_run(script_text)
metadata = _read_metadata_block(script_text) metadata = _read_metadata_block(script_text)
deps = metadata.get("dependencies", []) deps = metadata.get("dependencies", [])
if requires := metadata.get("requires-python", ""): if requires := metadata.get("requires-python", ""):
_check_python(requires) _check_python(requires)
if viv_used and deps: if has_use and deps:
error( error(
"Script Dependencies block and " "Script Dependencies block and "
"`viv.use` API can't be used in the same script" "`viv.use` API can't be used in the same script"
) )
if not self.local_source and viv_used: if not self.local_source and (has_use or has_run):
log.debug("fetching remote copy to use for python api") log.debug("fetching remote copy to use for python api")
(tmppath / "viv.py").write_text( (tmppath / "viv.py").write_text(
fetch_script( fetch_script(
@ -3427,10 +3459,9 @@ class Viv:
) )
) )
scriptpath.write_text(script_text)
self._update_cache(env, keep, tmpdir) self._update_cache(env, keep, tmpdir)
if viv_used: if has_use:
log.debug(f"script invokes viv.use passing along spec: \n '{spec}'") log.debug(f"script invokes viv.use passing along spec: \n '{spec}'")
subprocess_run_quit( subprocess_run_quit(
[sys.executable, "-S", scriptpath, *rest], [sys.executable, "-S", scriptpath, *rest],
@ -3440,6 +3471,16 @@ class Viv:
PYTHONPATH=":".join((str(tmppath), env.get("PYTHONPATH", ""))), PYTHONPATH=":".join((str(tmppath), env.get("PYTHONPATH", ""))),
), ),
) )
elif has_run:
log.debug("script invokes viv.run letting subprocess handle deps")
print(tmppath)
subprocess_run_quit(
[sys.executable, "-S", scriptpath, *rest],
env=dict(
env,
PYTHONPATH=":".join((str(tmppath), env.get("PYTHONPATH", ""))),
),
)
elif not spec and not deps: elif not spec and not deps:
log.warning("using viv with empty spec, skipping vivenv creation") log.warning("using viv with empty spec, skipping vivenv creation")
subprocess_run_quit([sys.executable, "-S", scriptpath, *rest]) subprocess_run_quit([sys.executable, "-S", scriptpath, *rest])