Skip to content

Commit 7bb763e

Browse files
committed
project: add pyprojectx
1 parent b115d91 commit 7bb763e

File tree

5 files changed

+203
-21
lines changed

5 files changed

+203
-21
lines changed

.github/workflows/check.yaml

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ name: check
22

33
on: push
44

5-
env:
6-
POETRY_VERSION: "1.7.1"
7-
85
jobs:
96
check:
107
runs-on: ubuntu-latest
@@ -27,11 +24,7 @@ jobs:
2724
- uses: actions/setup-python@v5
2825
with:
2926
python-version: ${{ matrix.python-version }}
30-
- name: Run image
31-
uses: abatilo/[email protected]
32-
with:
33-
poetry-version: ${{ env.POETRY_VERSION }}
34-
- run: poetry config virtualenvs.in-project true
27+
- run: ./pw poetry config virtualenvs.in-project true
3528
- name: Set up cache
3629
uses: actions/cache@v3
3730
id: cache
@@ -41,9 +34,9 @@ jobs:
4134
- name: Ensure cache is healthy
4235
if: steps.cache.outputs.cache-hit == 'true'
4336
run: timeout 10s poetry run pip --version || rm -rf .venv
44-
- run: poetry install
45-
- run: poetry run mypy -p basedtyping -p tests --python-version ${{ matrix.usable-python-version }}
46-
- run: poetry run pytest tests/
37+
- run: ./pw poetry install
38+
- run: ./pw poetry run mypy -p basedtyping -p tests --python-version ${{ matrix.usable-python-version }}
39+
- run: ./pw poetry run pytest tests/
4740

4841
lint:
4942
runs-on: ubuntu-latest
@@ -52,11 +45,7 @@ jobs:
5245
- uses: actions/setup-python@v5
5346
with:
5447
python-version: "3.8"
55-
- name: Run image
56-
uses: abatilo/[email protected]
57-
with:
58-
poetry-version: ${{ env.POETRY_VERSION }}
59-
- run: poetry config virtualenvs.in-project true
48+
- run: ./pw poetry config virtualenvs.in-project true
6049
- name: Set up cache
6150
uses: actions/cache@v3
6251
id: cache
@@ -65,7 +54,7 @@ jobs:
6554
key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }}
6655
- name: Ensure cache is healthy
6756
if: steps.cache.outputs.cache-hit == 'true'
68-
run: timeout 10s poetry run pip --version || rm -rf .venv
69-
- run: poetry install
70-
- run: poetry run ruff format --check --diff
71-
- run: poetry run ruff check
57+
run: timeout 10s ./pw poetry run pip --version || rm -rf .venv
58+
- run: ./pw poetry install
59+
- run: ./pw poetry run ruff format --check --diff
60+
- run: ./pw poetry run ruff check

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
**/__pycache__
22
/.venv/
33
/venv/
4-
/dist/
4+
/dist/
5+
/.pyprojectx

pw

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env python3
2+
3+
##################################################################################
4+
# Pyprojectx wrapper script #
5+
# https://github.com/pyprojectx/pyprojectx #
6+
# #
7+
# Copyright (c) 2021 Ivo Houbrechts #
8+
# #
9+
# Licensed under the MIT license #
10+
##################################################################################
11+
import argparse
12+
import os
13+
import subprocess
14+
import sys
15+
from pathlib import Path
16+
from venv import EnvBuilder
17+
18+
VERSION = "2.0.8"
19+
20+
PYPROJECTX_INSTALL_DIR_ENV_VAR = "PYPROJECTX_INSTALL_DIR"
21+
PYPROJECTX_PACKAGE_ENV_VAR = "PYPROJECTX_PACKAGE"
22+
PYPROJECT_TOML = "pyproject.toml"
23+
DEFAULT_INSTALL_DIR = ".pyprojectx"
24+
25+
CYAN = "\033[96m"
26+
BLUE = "\033[94m"
27+
RED = "\033[91m"
28+
RESET = "\033[0m"
29+
if sys.platform.startswith("win"):
30+
os.system("color")
31+
32+
33+
def run(args):
34+
try:
35+
options = get_options(args)
36+
pyprojectx_script = ensure_pyprojectx(options)
37+
explicit_options = []
38+
if not options.toml:
39+
explicit_options += ["--toml", str(options.toml_path)]
40+
if not options.install_dir:
41+
explicit_options += ["--install-dir", str(options.install_path)]
42+
43+
subprocess.run([str(pyprojectx_script), *explicit_options, *args], check=True)
44+
except subprocess.CalledProcessError as e:
45+
raise SystemExit(e.returncode) from e
46+
47+
48+
def get_options(args):
49+
options = arg_parser().parse_args(args)
50+
options.install_path = Path(
51+
options.install_dir
52+
or os.environ.get(PYPROJECTX_INSTALL_DIR_ENV_VAR, Path(__file__).with_name(DEFAULT_INSTALL_DIR))
53+
)
54+
options.toml_path = Path(options.toml) if options.toml else Path(__file__).with_name(PYPROJECT_TOML)
55+
if os.environ.get(PYPROJECTX_PACKAGE_ENV_VAR):
56+
options.version = "development"
57+
options.pyprojectx_package = os.environ.get(PYPROJECTX_PACKAGE_ENV_VAR)
58+
else:
59+
options.version = VERSION
60+
options.pyprojectx_package = f"pyprojectx~={VERSION}"
61+
options.verbosity = 0 if options.quiet or not options.verbosity else options.verbosity
62+
return options
63+
64+
65+
def arg_parser():
66+
parser = argparse.ArgumentParser(
67+
description="Execute commands or aliases defined in the [tool.pyprojectx] section of pyproject.toml. "
68+
"Use the -i or --info option to see available tools and aliases.",
69+
allow_abbrev=False,
70+
)
71+
parser.add_argument("--version", action="version", version=VERSION)
72+
parser.add_argument(
73+
"--toml",
74+
"-t",
75+
action="store",
76+
help="The toml config file. Defaults to 'pyproject.toml' in the same directory as the pw script.",
77+
)
78+
parser.add_argument(
79+
"--install-dir",
80+
action="store",
81+
help=f"The directory where all tools (including pyprojectx) are installed; defaults to the "
82+
f"{PYPROJECTX_INSTALL_DIR_ENV_VAR} environment value if set, else '.pyprojectx' "
83+
f"in the same directory as the invoked pw script",
84+
)
85+
parser.add_argument(
86+
"--force-install",
87+
"-f",
88+
action="store_true",
89+
help="Force clean installation of the virtual environment used to run cmd, if any",
90+
)
91+
parser.add_argument(
92+
"--install-context",
93+
action="store",
94+
metavar="tool-context",
95+
help="Install a tool context without actually running any command.",
96+
)
97+
parser.add_argument(
98+
"--verbose",
99+
"-v",
100+
action="count",
101+
dest="verbosity",
102+
help="Give more output. This option is additive and can be used up to 2 times.",
103+
)
104+
parser.add_argument(
105+
"--quiet",
106+
"-q",
107+
action="store_true",
108+
help="Suppress output",
109+
)
110+
parser.add_argument(
111+
"--info",
112+
"-i",
113+
action="store_true",
114+
help="Show the configuration details of a command instead of running it. "
115+
"If no command is specified, a list with all available tools and aliases is shown.",
116+
)
117+
parser.add_argument(
118+
"--add",
119+
action="store",
120+
metavar="[context:]<package>,<package>...",
121+
help="Add one or more packages to a tool context. "
122+
"If no context is specified, the packages are added to the main context. "
123+
"Packages can be specified as in 'pip install', except that a ',' can't be used in the version specification.",
124+
)
125+
parser.add_argument(
126+
"--lock",
127+
action="store_true",
128+
help="Write all dependencies of all tool contexts to 'pw.lock' to guarantee reproducible outcomes.",
129+
)
130+
parser.add_argument(
131+
"--install-px", action="store_true", help="Install the px and pxg scripts in your home directory."
132+
)
133+
parser.add_argument(
134+
"--upgrade",
135+
action="store_true",
136+
help="Print instructions to download the latest pyprojectx wrapper scripts.",
137+
)
138+
parser.add_argument(
139+
"command", nargs=argparse.REMAINDER, help="The command/alias with optional arguments to execute."
140+
)
141+
return parser
142+
143+
144+
def ensure_pyprojectx(options):
145+
env_builder = EnvBuilder(with_pip=True)
146+
venv_dir = options.install_path.joinpath(
147+
"pyprojectx", f"{options.version}-py{sys.version_info.major}.{sys.version_info.minor}"
148+
)
149+
env_context = env_builder.ensure_directories(venv_dir)
150+
pyprojectx_script = Path(env_context.bin_path, "pyprojectx")
151+
pyprojectx_exe = Path(env_context.bin_path, "pyprojectx.exe")
152+
pip_cmd = [env_context.env_exe, "-m", "pip", "install"]
153+
154+
if options.quiet:
155+
out = subprocess.DEVNULL
156+
pip_cmd.append("--quiet")
157+
else:
158+
out = sys.stderr
159+
160+
if not pyprojectx_script.is_file() and not pyprojectx_exe.is_file():
161+
if not options.quiet:
162+
print(f"{CYAN}creating pyprojectx venv in {BLUE}{venv_dir}{RESET}", file=sys.stderr)
163+
env_builder.create(venv_dir)
164+
subprocess.run(
165+
[*pip_cmd, "--upgrade", "pip"],
166+
stdout=out,
167+
check=True,
168+
)
169+
170+
if not options.quiet:
171+
print(
172+
f"{CYAN}installing pyprojectx {BLUE}{options.version}: {options.pyprojectx_package} {RESET}",
173+
file=sys.stderr,
174+
)
175+
if options.version == "development":
176+
if not options.quiet:
177+
print(
178+
f"{RED}WARNING: {options.pyprojectx_package} is installed in editable mode{RESET}",
179+
file=sys.stderr,
180+
)
181+
pip_cmd.append("-e")
182+
subprocess.run([*pip_cmd, options.pyprojectx_package], stdout=out, check=True)
183+
return pyprojectx_script
184+
185+
186+
if __name__ == "__main__":
187+
run(sys.argv[1:])

pw.bat

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@echo off
2+
python "%~dp0pw" %*

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ ruff = "~0.2.1"
2020
build-backend = "poetry.core.masonry.api"
2121
requires = ["poetry-core>=1.0.8"]
2222

23+
[tool.pyprojectx]
24+
main = ["poetry==1.7.1"]
25+
2326
[tool.mypy]
2427
python_version = 3.8
2528
packages = ["basedtyping", "tests"]

0 commit comments

Comments
 (0)