diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a9ac085fb..76ebba42d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,5 +19,18 @@ repos: hooks: # Run the linter. - id: ruff + # Run the formatter. - id: ruff-format + + # Run the linter in fix-only mode. + - id: ruff + alias: ruff-fix-only + stages: [manual] + args: [--fix-only] + + # Run the formatter in check mode. + - id: ruff-format + alias: ruff-format-check + stages: [manual] + args: [--check] diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e3b939594..6b9f41714 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -4,12 +4,14 @@ This is the simplest configuration for developers to start with. ### Initial Setup -1. Run `docker compose run --rm django ./manage.py migrate` -2. Run `docker compose run --rm django ./manage.py createcachetable` -3. Run `docker compose run --rm django ./manage.py createsuperuser --email $(git config user.email)` +1. Run `docker compose pull` to ensure you have the latest versions of the service container images. +2. Run `docker compose build --build-arg USERID=$(id -u) --build-arg GROUPID=$(id -g) --build-arg LOGIN=$(id -n -u) --build-arg GROUPNAME=$(id -n -g)` to build the development container image. This builds the image to work with your (non-root) development user so that the linting and formatting commands work inside and outside of the container. If you prefer to build the container image so that it runs as `root`, you can omit the `--build-arg` arguments (but you will likely run into trouble running those commands). +3. Run `docker compose run --rm django ./manage.py migrate` +4. Run `docker compose run --rm django ./manage.py createcachetable` +5. Run `docker compose run --rm django ./manage.py createsuperuser --email $(git config user.email)` and follow the prompts to create your own user. This sets your username to your git email to ensure parity with how GitHub logins work. You can also replace the command substitution expression with a literal email address, or omit the `--email` option entirely to run the command in interactive mode. -4. Run `docker compose run --rm django ./manage.py create_dev_dandiset --owner $(git config user.email)` +6. Run `docker compose run --rm django ./manage.py create_dev_dandiset --owner $(git config user.email)` to create a dummy dandiset to start working with. ### Run Application @@ -21,7 +23,7 @@ This is the simplest configuration for developers to start with. Occasionally, new package dependencies or schema changes will necessitate maintenance. To non-destructively update your development stack at any time: 1. Run `docker compose pull` -2. Run `docker compose build --pull --no-cache` +2. Run `docker compose build --pull --no-cache --build-arg USERID=$(id -u) --build-arg GROUPID=$(id -g) --build-arg LOGIN=$(id -n -u) --build-arg GROUPNAME=$(id -n -g)` (omitting the `--build-arg` arguments if you did so in Step 1 of *Initial Setup* above). 3. Run `docker compose run --rm django ./manage.py migrate` ## Develop Natively (advanced) diff --git a/dev/django.Dockerfile b/dev/django.Dockerfile index 069a9105d..841b673ec 100644 --- a/dev/django.Dockerfile +++ b/dev/django.Dockerfile @@ -1,4 +1,5 @@ FROM python:3.11-slim + # Install system librarires for Python packages: # * psycopg2 RUN apt-get update && \ @@ -6,9 +7,24 @@ RUN apt-get update && \ libpq-dev gcc libc6-dev git && \ rm -rf /var/lib/apt/lists/* +# Set some behaviors for Python. ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 +# Create a normal user (that can be made to match the dev's user stats), falling +# back to root; if the root user is specified, don't actually run the `adduser` +# command. +ARG USERID=0 +ARG LOGIN=root +ARG GROUPID=0 +ARG GROUPNAME=root +RUN getent group ${GROUPID} || addgroup --allow-all-names --gid ${GROUPID} ${GROUPNAME} +RUN getent passwd ${USERID} || adduser --allow-bad-names --uid ${USERID} --gid ${GROUPID} --home /home/${LOGIN} $LOGIN + +# Create the project folder and make the user its owner. +RUN mkdir -p /opt/django-project +RUN chown ${USERID}:${GROUPID} /opt/django-project + # Only copy the pyproject.toml, setup.py, and setup.cfg. It will still force all install_requires to be installed, # but find_packages() will find nothing (which is fine). When Docker Compose mounts the real source # over top of this directory, the .egg-link in site-packages resolves to the mounted directory @@ -17,13 +33,26 @@ COPY ./pyproject.toml /opt/django-project/pyproject.toml COPY ./setup.cfg /opt/django-project/setup.cfg COPY ./setup.py /opt/django-project/setup.py -# Copy git folder for setuptools_scm -COPY ./.git/ /opt/django-project/.git/ +# Copy the pre-commit config so that the pre-commit environments can be +# constructed later. +COPY ./.pre-commit-config.yaml /opt/django-project/.pre-commit-config.yaml + +# Copy git folder for setuptools_scm. Make it owned by the user so that the +# pre-commit prep steps later don't complain about "dubious ownership" of the +# git repository. +COPY --chown=${USERID}:${GROUPID} ./.git/ /opt/django-project/.git/ # Don't install as editable, so that when the directory is mounted over with `docker compose`, the # installation still exists (otherwise the dandiapi.egg-info/ directory would be overwritten, and # the installation would no longer exist) RUN pip install /opt/django-project[dev] +# Switch to the normal user. +USER $LOGIN + # Use a directory name which will never be an import name, as isort considers this as first-party. WORKDIR /opt/django-project + +# Run the pre-commit hooks (without --all-files, so that it won't actually run +# on any files) to create a reusable package cache. +RUN pre-commit diff --git a/setup.py b/setup.py index 7f7a58433..8c42655c1 100644 --- a/setup.py +++ b/setup.py @@ -89,6 +89,7 @@ 'django-stubs', 'djangorestframework-stubs', 'types-setuptools', + 'pre-commit', ], 'test': [ 'factory-boy', diff --git a/tox.ini b/tox.ini index 26e310864..05b3906c6 100644 --- a/tox.ini +++ b/tox.ini @@ -11,22 +11,19 @@ envlist = package = skip ignore_errors = True deps = - codespell~=2.0 - ruff~=0.6.2 + pre-commit commands = - ruff --version - ruff check - ruff format --check - codespell . + pre-commit run --all-files ruff + pre-commit run --all-files --hook-stage manual ruff-format-check + pre-commit run --all-files codespell [testenv:format] package = skip deps = - ruff~=0.6.2 + pre-commit commands = - ruff --version - ruff check --fix-only - ruff format + pre-commit run --all-files --hook-stage manual ruff-fix-only + pre-commit run --all-files ruff-format [testenv:type] deps =