From b9b77196ef3bc6ed897ff4a30269e72c96326c3b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:14:25 +0000 Subject: [PATCH] Update resonant-cookiecutter to v0.51.1 --- .copier-answers.resonant.yml | 2 +- .devcontainer/devcontainer.json | 91 +++++++++++++++ .devcontainer/docker-compose.devcontainer.yml | 10 ++ .editorconfig | 3 + .vscode/launch.json | 104 ++++++++++++++++++ .vscode/settings.json | 37 +++++++ Procfile | 7 ++ README.md | 35 ++++++ dandiapi/settings/heroku_production.py | 4 + dev/django.Dockerfile | 37 +++++++ dev/docker-development.md | 23 ++++ dev/export-env.sh | 8 ++ dev/native-development.md | 19 ++++ docker-compose.override.yml | 26 ++++- docker-compose.yml | 2 +- pyproject.toml | 4 + tox.ini | 3 +- 17 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 .devcontainer/docker-compose.devcontainer.yml create mode 100644 .vscode/settings.json create mode 100644 dev/docker-development.md create mode 100644 dev/native-development.md diff --git a/.copier-answers.resonant.yml b/.copier-answers.resonant.yml index b3597c89f..2b9b463c0 100644 --- a/.copier-answers.resonant.yml +++ b/.copier-answers.resonant.yml @@ -1,4 +1,4 @@ -_commit: v0.48.1 +_commit: v0.51.1 _src_path: https://github.com/kitware-resonant/cookiecutter-resonant core_app_name: api include_example_code: false diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ed34a6d8f..6b4eedce4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,7 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose { +<<<<<<< before updating "name": "DANDI Archive Django", "dockerComposeFile": [ "../docker-compose.yml", @@ -78,4 +79,94 @@ "--all-extras", "--all-groups" ] +======= + "name": "DANDI Archive", + "dockerComposeFile": [ + "../docker-compose.yml", + "../docker-compose.override.yml", + "./docker-compose.devcontainer.yml" + ], + "service": "django", + "overrideCommand": true, + // The "vscode" user and remoteUser are set by the base image label (devcontainers/base). + "workspaceFolder": "/home/vscode/dandiapi", + "features": { + "ghcr.io/devcontainers/features/git-lfs:1": {}, + "ghcr.io/devcontainers/features/node:2": { + "version": "24", + // Work around https://github.com/devcontainers/features/pull/1625 + "pnpmVersion": "none" + }, + "ghcr.io/rails/devcontainer/features/postgres-client:1": { + "version": 18 + }, + "ghcr.io/devcontainers/features/terraform:1": {}, + "ghcr.io/devcontainers/features/aws-cli:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers-extra/features/heroku-cli:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + // Python + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.debugpy", + "ms-python.mypy-type-checker", + "charliermarsh.ruff", + // Django + "batisteo.vscode-django", + "augustocdias.tasks-shell-input", + // Other file formats + "editorconfig.editorconfig", + "mikestead.dotenv", + "tamasfe.even-better-toml", + "timonwong.shellcheck", + // Infrastructure + "ms-azuretools.vscode-containers", + "hashicorp.terraform", + "github.vscode-github-actions", + // Remove AWS extension, as only the CLI is wanted; see: https://github.com/devcontainers/features/issues/1228 + "-AmazonWebServices.aws-toolkit-vscode" + ], + "settings": { + "containers.containerClient": "com.microsoft.visualstudio.containers.docker", + // Container-specific Python paths + "python.defaultInterpreterPath": "/home/vscode/venv/bin/python", + // Disable automatic Python venv activation in new terminals. + "python-envs.terminal.autoActivationType": "off", + // Ensure that `envFile` from any user settings is ignored; Docker Compose provides it. + "python.envFile": "", + // Reduce file watcher overhead for generated/cache directories. + "files.watcherExclude": { + "**/__pycache__/**": true, + "**/.pytest_cache/**": true, + "**/node_modules/**": true + } + } + } + }, + // Prevent a prompt every time the debugger opens a port or Django auto-restarts. + "otherPortsAttributes": { + "onAutoForward": "silent" + }, + "portsAttributes": { + "8000": { + "label": "Django", + // Show a dialog if the port isn't free. + "requireLocalPort": true, + "onAutoForward": "silent" + } + }, + // Install a global Python and create a venv before VSCode extensions start, + // to prevent prompts and ensure test discovery works on first load. + "onCreateCommand": { + "python": ["uv", "python", "install", "--default"], + "venv": ["uv", "sync", "--all-extras", "--all-groups"] + }, + // Ensure it is re-synced on restarts. + "updateContentCommand": { + "venv": ["uv", "sync", "--all-extras", "--all-groups"] + } +>>>>>>> after updating } diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml new file mode 100644 index 000000000..eb5cca88f --- /dev/null +++ b/.devcontainer/docker-compose.devcontainer.yml @@ -0,0 +1,10 @@ +services: + django: + # Don't expose ports, devcontainer forwarding is superior, since we can just bind to localhost. + ports: !reset [] + # Don't auto-run the default command, launch.json or the terminal will be used. + command: !reset [] + + celery: + # Celery will be started via launch.json or the terminal. + profiles: ["celery"] diff --git a/.editorconfig b/.editorconfig index b6494e09d..e6c9576e3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -27,6 +27,9 @@ indent_size = 2 indent_size = 4 max_line_length = 100 +[*.sh] +indent_size = 2 + [*.toml] indent_size = 2 diff --git a/.vscode/launch.json b/.vscode/launch.json index 72b82f51a..b9f454510 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,7 @@ { "version": "0.2.0", "configurations": [ +<<<<<<< before updating // Put Django Server first so it's the default when starting debugging { "name": "Python: Django Server", @@ -52,6 +53,109 @@ ], "console": "integratedTerminal", "justMyCode": false +======= + { + "name": "Django: Server", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/manage.py", + "args": ["runserver_plus", "--print-sql", "localhost:8000"], + "django": true, + "console": "integratedTerminal", + "justMyCode": false, + "presentation": { + "group": "2-services", + "order": 1 + } + }, + { + "name": "Django: Server (eager Celery)", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/manage.py", + "args": ["runserver_plus", "--print-sql", "localhost:8000"], + "env": { + "DJANGO_CELERY_TASK_ALWAYS_EAGER": "true" + }, + "django": true, + "console": "integratedTerminal", + "justMyCode": false, + "presentation": { + "group": "3-utilities", + "order": 1 + } + }, + { + "name": "Django: Management Command", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/manage.py", + "args": ["${input:managementCommand}"], + "django": true, + "console": "integratedTerminal", + "justMyCode": false, + "presentation": { + "group": "3-utilities", + "order": 2 + } + }, + { + "name": "Celery: Worker", + "type": "debugpy", + "request": "launch", + "module": "celery", + "args": [ + "--app", + "dandiapi.celery", + "worker", + "--loglevel", + "INFO", + "--without-mingle", + "--without-heartbeat", + "--without-gossip" + ], + "console": "integratedTerminal", + "justMyCode": false, + "presentation": { + "group": "2-services", + "order": 2 + } + }, + { + "name": "Pytest: Debug", + "type": "debugpy", + "request": "launch", + "purpose": ["debug-test"], + "console": "integratedTerminal", + "django": true, + "justMyCode": false, + "presentation": { + "hidden": true + } + } + ], + "compounds": [ + { + "name": "Django + Celery", + "configurations": ["Django: Server", "Celery: Worker"], + "stopAll": true, + "presentation": { + "group": "1-compound", + "order": 1 + } + } + ], + "inputs": [ + { + "id": "managementCommand", + "type": "command", + "command": "shellCommand.execute", + "args": { + "command": "./manage.py help --commands", + "description": "Django management command", + "allowCustomValues": true + } +>>>>>>> after updating } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..90cc42f9e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,37 @@ +{ + // File cleanup + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + + // Python + "python.analysis.autoFormatStrings": true, + "python.testing.pytestEnabled": true, + "python.analysis.autoImportCompletions": true, + "python.analysis.gotoDefinitionInStringLiteral": true, + // Allow auto-importing from deeper symbols inside of Django. + "python.analysis.packageIndexDepths": [ + { + "name": "django", + "depth": 6 + } + ], + "python.analysis.inlayHints.pytestParameters": true, + + // Django templates + "emmet.includeLanguages": { + "django-html": "html" + }, + + // Type checking: Use Mypy and disable Pylance. + "mypy-type-checker.importStrategy": "fromEnvironment", + // Mypy daemon seems better, but is buggy in practice. + "mypy-type-checker.preferDaemon": false, + "mypy-type-checker.reportingScope": "file", + "python.analysis.typeCheckingMode": "off", + + // Ruff + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + } +} diff --git a/Procfile b/Procfile index 37cc9ad44..79391c628 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,5 @@ release: python ./manage.py migrate +<<<<<<< before updating web: gunicorn --config gunicorn.conf.py dandiapi.wsgi # celery-beat: REMAP_SIGTERM=SIGQUIT celery --app dandiapi.celery beat --loglevel INFO # Rather than using a dedicated worker for Celery Beat, we simply use the -B option on the priority task worker. @@ -9,3 +10,9 @@ web: gunicorn --config gunicorn.conf.py dandiapi.wsgi worker: REMAP_SIGTERM=SIGQUIT celery --app dandiapi.celery worker --loglevel INFO --without-mingle --without-heartbeat --without-gossip --queues celery --beat # The checksum-worker calculates blob checksums and updates zarr checksum files checksum-worker: REMAP_SIGTERM=SIGQUIT celery --app dandiapi.celery worker --loglevel INFO --without-mingle --without-heartbeat --without-gossip --queues calculate_sha256,ingest_zarr_archive +======= +# Set `graceful_timeout` to shorter than the 30 second limit imposed by Heroku restarts +# Set `timeout` to shorter than the 30 second limit imposed by the Heroku router +web: gunicorn --bind 0.0.0.0:$PORT --graceful-timeout 25 --timeout 15 dandiapi.wsgi +worker: REMAP_SIGTERM=SIGQUIT celery --app dandiapi.celery worker --loglevel INFO --without-mingle --without-heartbeat --without-gossip +>>>>>>> after updating diff --git a/README.md b/README.md index d3323f425..89857938b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # DANDI Archive +<<<<<<< before updating ![](https://about.dandiarchive.org/assets/dandi_logo.svg) ## DANDI: Distributed Archives for Neurophysiology Data Integration @@ -29,3 +30,37 @@ see the [DANDI Docs](https://docs.dandiarchive.org). * To understand how to hack on the archive codebase: - Django backend: [`DEVELOPMENT.md`](DEVELOPMENT.md) - Vue frontend: [`web/README.md`](web/README.md) +======= +## Setup +1. Install [VS Code with dev container support](https://code.visualstudio.com/docs/devcontainers/containers#_installation). +1. Open the project in VS Code, then run `Dev Containers: Reopen in Container` + from the Command Palette (`Ctrl+Shift+P`). +1. Once the container is ready, open a terminal and run: + ```sh + ./manage.py migrate + ./manage.py createsuperuser + ``` + +## Run +Open the **Run and Debug** panel (`Ctrl+Shift+D`) and select a launch configuration: + +* **Django: Server** - Starts the development server at http://localhost:8000/ +* **Django: Server (eager Celery)** - Same, but Celery tasks run synchronously + in the web process (useful for debugging task code without a worker) +* **Celery: Worker** - Starts only the Celery worker +* **Django + Celery** - Starts both the server and a Celery worker +* **Django: Management Command** - Pick and run any management command + +## Test +Run the full test suite from a terminal: `tox` + +Auto-format code: `tox -e format` + +Run and debug individual tests from the **Testing** panel (`Ctrl+Shift+;`). + +## Rebuild +After changes to the Dockerfile, Docker Compose files, or `devcontainer.json`, +run `Dev Containers: Rebuild Container` from the Command Palette (`Ctrl+Shift+P`). + +For dependency changes in `pyproject.toml`, just run `uv sync --all-extras --all-groups`. +>>>>>>> after updating diff --git a/dandiapi/settings/heroku_production.py b/dandiapi/settings/heroku_production.py index 5a69ca6b0..651c0fb59 100644 --- a/dandiapi/settings/heroku_production.py +++ b/dandiapi/settings/heroku_production.py @@ -12,7 +12,11 @@ # This needs to be set by the HTTPS terminating reverse proxy. # Heroku and Render automatically set this. +<<<<<<< before updating SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +======= +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +>>>>>>> after updating # Inform rate limiting that "X-Forwarded-For" should be trusted, as it's appended by Heroku. ALLAUTH_TRUSTED_PROXY_COUNT = 1 diff --git a/dev/django.Dockerfile b/dev/django.Dockerfile index c559040da..e19914541 100644 --- a/dev/django.Dockerfile +++ b/dev/django.Dockerfile @@ -1,5 +1,6 @@ FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 +<<<<<<< before updating RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN mv /root/.local/bin/uv /usr/local/bin/uv RUN mv /root/.local/bin/uvx /usr/local/bin/uvx @@ -21,3 +22,39 @@ USER vscode RUN mkdir /home/vscode/uv WORKDIR /home/vscode/dandi +======= +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ + +# Ensure Python output appears immediately in container logs. +ENV PYTHONUNBUFFERED=1 + +# Override Node's default of attempting to bind to IPv6 interfaces over IPv4 +ENV NODE_OPTIONS=--dns-result-order=ipv4first + +# Put the uv and npm caches in a separate location, +# where they can persist and be shared across containers. +# The uv cache and virtual environment are on different volumes, so hardlinks won't work. +ENV UV_CACHE_DIR=/home/vscode/pkg-cache/uv \ + UV_PYTHON_INSTALL_DIR=/home/vscode/pkg-cache/uv-python \ + UV_LINK_MODE=symlink \ + NPM_CONFIG_CACHE=/home/vscode/pkg-cache/npm + +# Put the virtual environment outside the project directory, +# to improve performance on macOS and prevent accidental usage from the host machine. +# Activate it, so `uv run` doesn't need to be prefixed. +ENV UV_PROJECT_ENVIRONMENT=/home/vscode/venv \ + PATH="/home/vscode/venv/bin:$PATH" + +# Put tool scratch files outside the project directory too. +ENV TOX_WORK_DIR=/home/vscode/tox \ + RUFF_CACHE_DIR=/home/vscode/.cache/ruff \ + MYPY_CACHE_DIR=/home/vscode/.cache/mypy + +RUN ["chsh", "-s", "/usr/bin/zsh", "vscode"] + +USER vscode + +# Pre-create named volume mount points, so the new volume inherits `vscode` user ownership: +# https://docs.docker.com/engine/storage/volumes/#populate-a-volume-using-a-container +RUN ["mkdir", "/home/vscode/pkg-cache"] +>>>>>>> after updating diff --git a/dev/docker-development.md b/dev/docker-development.md new file mode 100644 index 000000000..a5faba3c7 --- /dev/null +++ b/dev/docker-development.md @@ -0,0 +1,23 @@ +# Docker Compose Development (without VS Code) + +An alternative to the recommended [dev container](../README.md) workflow. + +## Setup +1. `docker compose run --rm django ./manage.py migrate` +1. `docker compose run --rm django ./manage.py createsuperuser` + +## Run +1. `docker compose up` +1. Access http://localhost:8000/ +1. `Ctrl+C` to stop + +To include the Celery worker: `docker compose --profile celery up` + +## Update +1. `docker compose down` +1. `docker compose pull` +1. `docker compose build --pull` +1. `docker compose run --rm django ./manage.py migrate` + +## Reset +Remove all data and volumes: `docker compose down -v` diff --git a/dev/export-env.sh b/dev/export-env.sh index ee0e3b0e3..3e866bc26 100644 --- a/dev/export-env.sh +++ b/dev/export-env.sh @@ -1,3 +1,7 @@ +<<<<<<< before updating +======= +# shellcheck shell=bash +>>>>>>> after updating # Export environment variables from the .env file in the first argument. # If no argument is given, default to "dev/.env.docker-compose-native". # This file must be sourced, not run. @@ -21,6 +25,10 @@ fi # Using "set -a" allows .env files with spaces or comments to work seamlessly # https://stackoverflow.com/a/45971167 set -a +<<<<<<< before updating +======= +# shellcheck source=.env.docker-compose-native +>>>>>>> after updating . "$_dotenv_file" set +a diff --git a/dev/native-development.md b/dev/native-development.md new file mode 100644 index 000000000..085764aab --- /dev/null +++ b/dev/native-development.md @@ -0,0 +1,19 @@ +# Native Development (advanced) + +Runs Python on the host while using Docker Compose for services. + +## Setup +1. [Install `uv`](https://docs.astral.sh/uv/getting-started/installation/) +1. Start services: `docker compose -f ./docker-compose.yml up -d` +1. Load environment: `source ./dev/export-env.sh` +1. `./manage.py migrate` +1. `./manage.py createsuperuser` + +## Run +1. Ensure services are running: `docker compose -f ./docker-compose.yml up -d` +1. `source ./dev/export-env.sh` +1. `./manage.py runserver_plus` +1. In a separate terminal: `celery --app dandiapi.celery worker --loglevel INFO --without-mingle --without-heartbeat --without-gossip` +1. Access http://localhost:8000/ + +Stop services when done: `docker compose stop` diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 0efb29712..2746ebaae 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -9,6 +9,7 @@ services: ] # Log printing is enhanced by a TTY tty: true +<<<<<<< before updating environment: PYTHONDONTWRITEBYTECODE: 1 PYTHONUNBUFFERED: 1 @@ -24,6 +25,13 @@ services: - .:/home/vscode/dandi - uv_cache:/home/vscode/uv - pre-commit_cache:/home/vscode/.cache/pre-commit +======= + working_dir: /home/vscode/dandiapi + env_file: ./dev/.env.docker-compose + volumes: + - .:/home/vscode/dandiapi + - pkg_cache:/home/vscode/pkg-cache +>>>>>>> after updating ports: - 8000:8000 depends_on: @@ -46,12 +54,17 @@ services: "--loglevel", "INFO", "--without-mingle", "--without-heartbeat", +<<<<<<< before updating "--without-gossip", "--queues", "celery,calculate_sha256,ingest_zarr_archive,manifest-worker", "--beat" +======= + "--without-gossip" +>>>>>>> after updating ] - # Docker Compose does not set the TTY width, which causes Celery errors + # uv progress doesn't display properly with a Docker TTY tty: false +<<<<<<< before updating environment: PYTHONDONTWRITEBYTECODE: 1 PYTHONUNBUFFERED: 1 @@ -68,6 +81,13 @@ services: - .:/home/vscode/dandi - uv_cache:/home/vscode/uv - pre-commit_cache:/home/vscode/.cache/pre-commit +======= + working_dir: /home/vscode/dandiapi + env_file: ./dev/.env.docker-compose + volumes: + - .:/home/vscode/dandiapi + - pkg_cache:/home/vscode/pkg-cache +>>>>>>> after updating depends_on: postgres: condition: service_healthy @@ -77,5 +97,9 @@ services: condition: service_healthy volumes: +<<<<<<< before updating uv_cache: pre-commit_cache: +======= + pkg_cache: +>>>>>>> after updating diff --git a/docker-compose.yml b/docker-compose.yml index a518d5c5b..9b643857a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,7 @@ services: - postgres:/var/lib/postgresql rabbitmq: - image: rabbitmq:management-alpine + image: rabbitmq:4.3-management-alpine healthcheck: test: ["CMD", "rabbitmq-diagnostics", "ping"] start_period: 30s diff --git a/pyproject.toml b/pyproject.toml index cafc74141..b4fcc9738 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -198,6 +198,10 @@ extend-immutable-calls = ["ninja.Query"] [tool.ruff.lint.flake8-self] extend-ignore-names = ["_base_manager", "_default_manager", "_meta"] +[tool.ruff.lint.flake8-type-checking] +runtime-evaluated-base-classes = ["pydantic.BaseModel"] +runtime-evaluated-decorators = ["pydantic.validate_call"] + [tool.ruff.lint.isort] # Sort by name, don't cluster "from" vs "import" force-sort-within-sections = true diff --git a/tox.ini b/tox.ini index b5d0aba57..655beb16a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,4 @@ [tox] -min_version = 4.22 requires = tox-uv envlist = @@ -12,6 +11,8 @@ envlist = runner = uv-venv-lock-runner pass_env = DJANGO_* + RUFF_CACHE_DIR + MYPY_CACHE_DIR extras = development