diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fe4dd88..ffdab18 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,8 +18,12 @@ jobs: with: python-version: "3.x" - - name: install cookiecutter - run: python -m pip install cookiecutter + - name: install uv + run: > + curl --no-progress-meter --location --fail + --proto '=https' --tlsv1.2 + "https://astral.sh/uv/install.sh" + | sh # we need the git config setup here to make sure the subsequent git commit in each test works - name: setup fake git committer @@ -29,7 +33,7 @@ jobs: - name: run template (default) run: | - cookiecutter --no-input -o /tmp . + uvx cookiecutter --no-input -o /tmp . [[ -d /tmp/python-project/src/python_project ]] || { >&2 echo "not generated?"; exit 1; } @@ -48,7 +52,7 @@ jobs: - name: run template (no entry point) run: | - cookiecutter --no-input -o /tmp . entry_point='' + uvx cookiecutter --no-input -o /tmp . entry_point='' [[ ! -f /tmp/python-project/python_project/__main__.py ]] || { >&2 echo "not expecting main"; exit 1; } @@ -67,7 +71,7 @@ jobs: - name: run template (namespace) run: | - cookiecutter --no-input -o /tmp . project_namespace_import=tob.r_and_e + uvx cookiecutter --no-input -o /tmp . project_namespace_import=tob.r_and_e [[ -d /tmp/tob-r-and-e-python-project/src/tob/r_and_e/python_project ]] || { >&2 echo "not generated?"; exit 1; } @@ -86,7 +90,7 @@ jobs: - name: run template (namespace, short slug) run: | - cookiecutter --no-input -o /tmp . project_namespace_import=tob.r_and_e "project_name=Bit Trails" project_slug=bit-trails + uvx cookiecutter --no-input -o /tmp . project_namespace_import=tob.r_and_e "project_name=Bit Trails" project_slug=bit-trails [[ -d /tmp/bit-trails/src/tob/r_and_e/bit_trails ]] || { >&2 echo "not generated?"; exit 1; } @@ -105,7 +109,7 @@ jobs: - name: run template (no docs) run: | - cookiecutter --no-input -o /tmp . documentation='none' + uvx cookiecutter --no-input -o /tmp . documentation='none' [[ -d /tmp/python-project/src/python_project ]] || { >&2 echo "not generated?"; exit 1; } [[ ! -f /tmp/python-project/.github/workflows/docs.yml ]] || { >&2 echo "not expecting docs.yml"; exit 1; } diff --git a/README.md b/README.md index c132694..29a5d28 100644 --- a/README.md +++ b/README.md @@ -11,28 +11,42 @@ Both command-line and library packages can be generated. ## Usage -Install the `cookiecutter` CLI: +- Install [`uv`](https://docs.astral.sh/uv/) ```bash -python -m pip install cookiecutter +# On any platform with cURL and sh +curl -LsSf https://astral.sh/uv/install.sh | sh -# or, if you have pipx -pipx install cookiecutter +# On Windows +powershell -c "irm https://astral.sh/uv/install.ps1 | iex" + +# With Brew +brew install uv ``` -Use `cookiecutter` to generate a project using this template: +Please refer to [`uv` documentation](https://docs.astral.sh/uv/getting-started/installation/) for more installation options. + +- Use `cookiecutter` to generate a project using this template: ```bash # creates the project directory in $PWD -cookiecutter gh:trailofbits/cookiecutter-python +uvx cookiecutter gh:trailofbits/cookiecutter-python ``` Alternatively, tell `cookiecutter` where to put the new project directory: ```bash # creates ~/tmp/$project -cookiecutter -o ~/tmp gh:trailofbits/cookiecutter-python +uvx cookiecutter -o ~/tmp gh:trailofbits/cookiecutter-python ``` `cookiecutter` will prompt you for the project's name and all other relevant metadata. + +## Note + +The project will be generated using `uv`, a near drop-in replacement for +`pip` which is still in active development. + +Packages are installed using `uv pip install ` (after +activating the virtual environment). \ No newline at end of file diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 846a135..ff8ac06 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -1,5 +1,4 @@ import os -import sys REMOVE_PATHS = [ # We delete _cli.py and __main__.py if we're not generating a CLI. diff --git a/{{cookiecutter.project_slug}}/.github/workflows/docs.yml b/{{cookiecutter.project_slug}}/.github/workflows/docs.yml index 79cb494..836d3ae 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/docs.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/docs.yml @@ -16,6 +16,13 @@ jobs: python-version-file: pyproject.toml cache: "pip" cache-dependency-path: pyproject.toml + + - name: install uv + run: > + curl --no-progress-meter --location --fail + --proto '=https' --tlsv1.2 + "https://astral.sh/uv/install.sh" + | sh - name: setup run: | diff --git a/{{cookiecutter.project_slug}}/.github/workflows/lint.yml b/{{cookiecutter.project_slug}}/.github/workflows/lint.yml index f484a39..41faacc 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/lint.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/lint.yml @@ -17,6 +17,13 @@ jobs: python-version-file: pyproject.toml cache: "pip" cache-dependency-path: pyproject.toml + + - name: install uv + run: > + curl --no-progress-meter --location --fail + --proto '=https' --tlsv1.2 + "https://astral.sh/uv/install.sh" + | sh - name: lint run: make lint INSTALL_EXTRA=lint diff --git a/{{cookiecutter.project_slug}}/.github/workflows/tests.yml b/{{cookiecutter.project_slug}}/.github/workflows/tests.yml index 9bd0005..6641fb6 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/tests.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/tests.yml @@ -24,6 +24,13 @@ jobs: python-version: ${{ matrix.python }} cache: "pip" cache-dependency-path: pyproject.toml + + - name: install uv + run: > + curl --no-progress-meter --location --fail + --proto '=https' --tlsv1.2 + "https://astral.sh/uv/install.sh" + | sh - name: test run: make test INSTALL_EXTRA=test diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 687ff6b..d150226 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -46,12 +46,14 @@ dev: $(VENV)/pyvenv.cfg {%- if cookiecutter.entry_point %} .PHONY: run run: $(VENV)/pyvenv.cfg + # Once we can specify the default VENV name in uv, use `uv run` here + # https://github.com/astral-sh/uv/issues/1422 @. $(VENV_BIN)/activate && {{ cookiecutter.entry_point }} $(ARGS) {%- endif %} $(VENV)/pyvenv.cfg: pyproject.toml - python -m venv env - $(VENV_BIN)/python -m pip install -e .[$(INSTALL_EXTRA)] + uv venv $(VENV) + @. $(VENV_BIN)/activate && uv pip install -e '.[$(INSTALL_EXTRA)]' .PHONY: lint lint: $(VENV)/pyvenv.cfg @@ -68,8 +70,8 @@ lint: $(VENV)/pyvenv.cfg .PHONY: reformat reformat: . $(VENV_BIN)/activate && \ - ruff check --fix && \ - ruff format + ruff format && \ + ruff check --fix .PHONY: test tests test tests: $(VENV)/pyvenv.cfg @@ -89,7 +91,7 @@ doc: .PHONY: package package: $(VENV)/pyvenv.cfg - $(VENV_BIN)/python -m build + uvx --from build pyproject-build --installer uv .PHONY: edit edit: diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index bed568d..8c8a098 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -30,7 +30,7 @@ test = ["pytest", "pytest-cov", "pretend", "coverage[toml]"] lint = [ # NOTE: ruff is under active development, so we pin conservatively here # and let Dependabot periodically perform this update. - "ruff ~= 0.4.0", + "ruff ~= 0.6.2", "mypy >= 1.0", "types-html5lib", "types-requests", @@ -77,7 +77,7 @@ warn_unused_ignores = true [tool.ruff] line-length = 100 -target-version = "py38" +include = ["src/**/*.py", "test/**/*.py"] [tool.ruff.lint] select = ["ALL"] @@ -97,9 +97,11 @@ ignore = ["D203", "D213", "COM812", "ISC001"] "S101", # asserts are expected in tests ] +{%- if cookiecutter.docstring_coverage %} [tool.interrogate] # don't enforce documentation coverage for packaging, testing, the virtual # environment, or the CLI (which is documented separately). exclude = ["env", "test", "{{ cookiecutter.__project_src_path }}/_cli.py"] ignore-semiprivate = true fail-under = 100 +{%- endif %} \ No newline at end of file