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

Add uv as package manger for the generated project #5434

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ and then editing the results to include your name, email, and various configurat

First, get Cookiecutter. Trust me, it's awesome:

$ pip install "cookiecutter>=1.7.0"
$ uv tool install "cookiecutter>=1.7.0"

Now run it against this repo:

$ cookiecutter https://github.com/cookiecutter/cookiecutter-django
$ uvx cookiecutter https://github.com/cookiecutter/cookiecutter-django

You'll be prompted for some values. Provide them, then a Django project will be created for you.

Expand Down
6 changes: 3 additions & 3 deletions docs/2-local-development/developing-locally-docker.rst
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ This tells our computer that all future commands are specifically for the dev1 m
Add 3rd party python packages
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To install a new 3rd party python package, you cannot use ``pip install <package_name>``, that would only add the package to the container. The container is ephemeral, so that new library won't be persisted if you run another container. Instead, you should modify the Docker image:
You have to modify the relevant requirement file: base, local or production by adding: ::
To install a new 3rd party python package, you cannot use ``uv add <package_name>``, that would only add the package to the container. The container is ephemeral, so that new library won't be persisted if you run another container. Instead, you should modify the Docker image:
You have to modify pyproject.toml and either add it to project.dependencies or to tool.uv.dev-dependencies by adding: ::

<package_name>==<package_version>
"<package_name>==<package_version>"

To get this change picked up, you'll need to rebuild the image(s) and restart the running container: ::

Expand Down
30 changes: 10 additions & 20 deletions docs/2-local-development/developing-locally.rst
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
Getting Up and Running Locally
==============================

.. index:: pip, virtualenv, PostgreSQL
.. index:: PostgreSQL


Setting Up Development Environment
----------------------------------

Make sure to have the following on your host:

* Python 3.12
* uv https://docs.astral.sh/uv/getting-started/installation/
* PostgreSQL_.
* Redis_, if using Celery
* Cookiecutter_

First things first.

#. Create a virtualenv: ::

$ python3.12 -m venv <virtual env path>

#. Activate the virtualenv you have just created: ::

$ source <virtual env path>/bin/activate

#. .. include:: generate-project-block.rst

#. Install development requirements: ::

$ cd <what you have entered as the project_slug at setup stage>
$ pip install -r requirements/local.txt
$ uv sync
$ git init # A git repo is required for pre-commit to install
$ pre-commit install
$ uv run pre-commit install

.. note::

Expand Down Expand Up @@ -71,15 +61,15 @@ First things first.

#. Apply migrations: ::

$ python manage.py migrate
$ uv run python manage.py migrate

#. If you're running synchronously, see the application being served through Django development server: ::

$ python manage.py runserver 0.0.0.0:8000
$ uv run python manage.py runserver 0.0.0.0:8000

or if you're running asynchronously: ::

$ uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html'
$ uv run uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html'

If you've opted for Webpack or Gulp as frontend pipeline, please see the :ref:`dedicated section <bare-metal-webpack-gulp>` below.

Expand Down Expand Up @@ -136,7 +126,7 @@ Following this structured approach, here's how to add a new app:

#. **Create the app** using Django's ``startapp`` command, replacing ``<name-of-the-app>`` with your desired app name: ::

$ python manage.py startapp <name-of-the-app>
$ uv run python manage.py startapp <name-of-the-app>

#. **Move the app** to the Django Project Root, maintaining the project's two-tier structure: ::

Expand Down Expand Up @@ -203,14 +193,14 @@ Next, make sure `redis-server` is installed (per the `Getting started with Redis

Start the Celery worker by running the following command in another terminal::

$ celery -A config.celery_app worker --loglevel=info
$ uv run celery -A config.celery_app worker --loglevel=info

That Celery worker should be running whenever your app is running, typically as a background process,
so that it can pick up any tasks that get queued. Learn more from the `Celery Workers Guide`_.

The project comes with a simple task for manual testing purposes, inside `<project_slug>/users/tasks.py`. To queue that task locally, start the Django shell, import the task, and call `delay()` on it::

$ python manage.py shell
$ uv run python manage.py shell
>> from <project_slug>.users.tasks import get_users_count
>> get_users_count.delay()

Expand Down
2 changes: 1 addition & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXBUILD = uv run sphinx-build
SOURCEDIR = .
BUILDDIR = _build

Expand Down
22 changes: 14 additions & 8 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import random
import shutil
import string
from pathlib import Path

try:
# Inspired by
Expand Down Expand Up @@ -100,7 +101,7 @@ def remove_utility_files():


def remove_heroku_files():
file_names = ["Procfile", "runtime.txt", "requirements.txt"]
file_names = ["Procfile", "runtime.txt"]
for file_name in file_names:
if file_name == "requirements.txt" and "{{ cookiecutter.ci_tool }}".lower() == "travis":
# don't remove the file if we are using travisci but not using heroku
Expand Down Expand Up @@ -218,21 +219,25 @@ def handle_js_runner(choice, use_docker, use_async):


def remove_prettier_pre_commit():
with open(".pre-commit-config.yaml", "r") as fd:
content = fd.readlines()
remove_repo_from_pre_commit_config("mirrors-prettier")


def remove_repo_from_pre_commit_config(repo_to_remove: str):
pre_commit_config = Path(".pre-commit-config.yaml")
content = pre_commit_config.read_text().splitlines(True)

removing = False
new_lines = []
new_lines = ""

for line in content:
if removing and "- repo:" in line:
removing = False
if "mirrors-prettier" in line:
if repo_to_remove in line:
removing = True
if not removing:
new_lines.append(line)
new_lines += line

with open(".pre-commit-config.yaml", "w") as fd:
fd.writelines(new_lines)
pre_commit_config.write_text(new_lines)


def remove_celery_files():
Expand Down Expand Up @@ -471,6 +476,7 @@ def main():

if "{{ cookiecutter.use_heroku }}".lower() == "n":
remove_heroku_files()
remove_repo_from_pre_commit_config("uv-pre-commit")

if "{{ cookiecutter.use_docker }}".lower() == "n" and "{{ cookiecutter.use_heroku }}".lower() == "n":
if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y":
Expand Down
8 changes: 4 additions & 4 deletions tests/test_bare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ cd my_awesome_project
sudo utility/install_os_dependencies.sh install

# Install Python deps
pip install -r requirements/local.txt
uv sync

# run the project's tests
pytest
uv run pytest

# Make sure the check doesn't raise any warnings
python manage.py check --fail-level WARNING
uv run python manage.py check --fail-level WARNING

# Run npm build script if package.json is present
if [ -f "package.json" ]
Expand All @@ -34,4 +34,4 @@ then
fi

# Generate the HTML for the documentation
cd docs && make html
cd docs && uv run make html
45 changes: 41 additions & 4 deletions tests/test_cookiecutter_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import re
import sys
import tomllib

import pytest

Expand Down Expand Up @@ -273,7 +274,7 @@ def test_djlint_check_passes(cookies, context_override):
@pytest.mark.parametrize(
["use_docker", "expected_test_script"],
[
("n", "pytest"),
("n", "uv run pytest"),
("y", "docker compose -f docker-compose.local.yml run django pytest"),
],
)
Expand All @@ -298,7 +299,7 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip
@pytest.mark.parametrize(
["use_docker", "expected_test_script"],
[
("n", "pytest"),
("n", "uv run pytest"),
("y", "docker compose -f docker-compose.local.yml run django pytest"),
],
)
Expand All @@ -315,7 +316,7 @@ def test_gitlab_invokes_precommit_and_pytest(cookies, context, use_docker, expec
try:
gitlab_config = yaml.safe_load(gitlab_yml)
assert gitlab_config["precommit"]["script"] == [
"pre-commit run --show-diff-on-failure --color=always --all-files"
"uv run pre-commit run --show-diff-on-failure --color=always --all-files"
]
assert gitlab_config["pytest"]["script"] == [expected_test_script]
except yaml.YAMLError as e:
Expand All @@ -325,7 +326,7 @@ def test_gitlab_invokes_precommit_and_pytest(cookies, context, use_docker, expec
@pytest.mark.parametrize(
["use_docker", "expected_test_script"],
[
("n", "pytest"),
("n", "uv run pytest"),
("y", "docker compose -f docker-compose.local.yml run django pytest"),
],
)
Expand Down Expand Up @@ -412,3 +413,39 @@ def test_trim_domain_email(cookies, context):

base_settings = result.project_path / "config" / "settings" / "base.py"
assert '"[email protected]"' in base_settings.read_text()


def test_pyproject_toml(cookies, context):
author_name = "Project Author"
author_email = "[email protected]"
context.update(
{
"description": "DESCRIPTION",
"domain_name": "example.com",
"email": author_email,
"author_name": author_name,
}
)
result = cookies.bake(extra_context=context)
assert result.exit_code == 0

pyproject_toml = result.project_path / "pyproject.toml"

data = tomllib.loads(pyproject_toml.read_text())

assert data
assert data["project"]["authors"][0]["email"] == author_email
assert data["project"]["authors"][0]["name"] == author_name
assert data["project"]["name"] == context["project_slug"]


def test_pre_commit_without_heroku(cookies, context):
context.update({"use_heroku": "n"})
result = cookies.bake(extra_context=context)
assert result.exit_code == 0

pre_commit_config = result.project_path / ".pre-commit-config.yaml"

data = pre_commit_config.read_text()

assert "uv-pre-commit" not in data
14 changes: 14 additions & 0 deletions tests/test_docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@

set -o errexit
set -x
set -e

finish() {
# Your cleanup code here
docker compose -f docker-compose.local.yml down --remove-orphans
docker volume rm my_awesome_project_my_awesome_project_local_postgres_data

}
trap finish EXIT

# create a cache directory
mkdir -p .cache/docker
cd .cache/docker

sudo rm -rf my_awesome_project

# create the project using the default settings in cookiecutter.json
uv run cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y "$@"
cd my_awesome_project
Expand All @@ -20,6 +31,8 @@ docker compose -f docker-compose.local.yml build
# run the project's type checks
docker compose -f docker-compose.local.yml run django mypy my_awesome_project

docker compose -f docker-compose.local.yml run django uv lock

# run the project's tests
docker compose -f docker-compose.local.yml run django pytest

Expand All @@ -44,6 +57,7 @@ docker compose -f docker-compose.local.yml run \
# Generate the HTML for the documentation
docker compose -f docker-compose.docs.yml run docs make html

docker build -f ./compose/production/django/Dockerfile .
# Run npm build script if package.json is present
if [ -f "package.json" ]
then
Expand Down
4 changes: 2 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions {{cookiecutter.project_slug}}/.drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ environment:
steps:
- name: lint
pull: if-not-exists
image: python:3.12
image: ghcr.io/astral-sh/uv:python3.12
environment:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
volumes:
- name: pre-commit cache
path: ${PRE_COMMIT_HOME}
commands:
- export PRE_COMMIT_HOME=$CI_PROJECT_DIR/.cache/pre-commit
- pip install -q pre-commit
- pre-commit run --show-diff-on-failure --color=always --all-files
- uv pip install -q pre-commit pre-commit-uv
- uv run pre-commit run --show-diff-on-failure --color=always --all-files

- name: test
pull: if-not-exists
Expand All @@ -37,10 +37,10 @@ steps:
- docker-compose -f docker-compose.local.yml up -d
- docker-compose -f docker-compose.local.yml run django pytest
{%- else %}
image: python:3.12
image: ghcr.io/astral-sh/uv:python3.12
commands:
- pip install -r requirements/local.txt
- pytest
- uv sync --frozen
- uv run pytest
{%- endif%}

volumes:
Expand Down
Loading