Skip to content
Merged
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
3 changes: 2 additions & 1 deletion template/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ env
.envrc.local

# vs code
.vscode/
.vscode/*
!.vscode/launch.json
.history/

# terraform files
Expand Down
15 changes: 15 additions & 0 deletions template/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Django (debugpy in k8s)",
"type": "debugpy",
"request": "attach",
"connect": { "host": "127.0.0.1", "port": 5678 },
"pathMappings": [
{ "localRoot": "${workspaceFolder}/backend", "remoteRoot": "/app/src" }
],
"justMyCode": false
}
]
}
2 changes: 1 addition & 1 deletion template/Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ syncback(
{% if copier__create_nextjs_frontend %}
k8s_resource(workload='frontend', port_forwards=3000)
{% endif %}
k8s_resource(workload='backend', port_forwards=8000)
k8s_resource(workload='backend', port_forwards=[8000, 5678])
k8s_resource(workload='mailhog', port_forwards=8025)
k8s_resource(workload='postgres', port_forwards=5432)
{% if copier__use_celery %}
Expand Down
21 changes: 20 additions & 1 deletion template/backend/{{copier__project_slug}}/utils/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,23 @@ def pycharm_debugger():


def vscode_debugger():
raise NotImplementedError("VSCode debugger not implemented")
logger.info("VSCode debugpy starting...")
import debugpy

# debugpy.listen() may only be called once per process; breakpoint() can fire
# multiple times, and runserver's reloader runs request code in the RUN_MAIN
# worker. Guard against re-listen; wait for the IDE to attach before pausing.
if not debugpy.is_client_connected():
try:
# Bind all interfaces so the port-forward can reach the pod; this
# debug server is dev-only (active only when PYTHONBREAKPOINT is set).
debugpy.listen(("0.0.0.0", 5678)) # nosec B104
logger.info(
"debugpy listening on 0.0.0.0:5678 - waiting for VSCode to attach..."
)
except RuntimeError:
logger.info("debugpy already listening; waiting for client.")
debugpy.wait_for_client()
logger.info("VSCode debugger attached.")

debugpy.breakpoint()
46 changes: 42 additions & 4 deletions template/docs/debug.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# :bug: How to debug the application

The steps below describe how to set up interactive debugging with PyCharm.
The steps below describe how to set up interactive debugging with PyCharm or VSCode.

## PyCharm Debugging Setup
Update `k8s/base/app.configmap.yaml` with `data` field `PYTHONBREAKPOINT: "utils.pycharm_debugger"`
Set `PYTHONBREAKPOINT: "utils.pycharm_debugger"` in the `app-config` patch in
`k8s/local/kustomization.yaml` (debugger config is local-only so it can't reach sandbox/prod).

In PyCharm:

Expand All @@ -19,11 +20,48 @@ In PyCharm:

![debug__debug_configuration.png](images/debug__debug_configuration.png)

## VSCode Debugging Setup

VSCode uses [debugpy](https://github.com/microsoft/debugpy) (already in `requirements/local.in`).
Unlike PyCharm, the pod runs the debug *server* and VSCode *attaches* to it, so Tilt
forwards the debug port (`5678`) into the pod for you.

1. Set `PYTHONBREAKPOINT: "{{ copier__project_slug }}.utils.vscode_debugger"` in the
`app-config` patch in `k8s/local/kustomization.yaml` (debugger config is local-only).
2. Run `tilt up`. Confirm the `backend` resource forwards both `8000` and `5678`.
3. A `.vscode/launch.json` with an "Attach to Django (debugpy in k8s)" configuration is
already included at the project root.

## Debugging in development
Before the code you want to debug, add the following:

`PYTHONBREAKPOINT` (set above) routes Python's built-in `breakpoint()` to the debugger for
your IDE. Before the code you want to inspect, add:

```python
breakpoint()
```

You must then set break points in your IDE and call the code as usual to hit them.
Then call the code as usual (e.g. hit the endpoint). What happens when `breakpoint()` is
reached differs by IDE:

### PyCharm

Hitting `breakpoint()` only *connects* the debugger — it does **not** suspend on that line
(`suspend=False`). You must set break points in the PyCharm gutter at the lines you want to
stop on; execution pauses when it reaches one of those.

### VSCode

`breakpoint()` **is** the stop point — you do not need to set gutter breakpoints:

1. Add `breakpoint()` and trigger the code. The request **blocks** at that line waiting for
a debugger to attach (debugpy starts listening on the first `breakpoint()` that is hit).
2. In VSCode, open the Run and Debug panel and start "Attach to Django (debugpy in k8s)".
Execution resumes and pauses right at your `breakpoint()`.

Once attached you *may* optionally set additional gutter breakpoints in VSCode and they'll be
hit on subsequent runs, but a plain `breakpoint()` is sufficient on its own.

> :warning: Because the request blocks until you attach, a `breakpoint()` you never attach
> to will hang that request indefinitely. This is dev-only (local readiness/liveness probes
> are disabled), but remember to remove `breakpoint()` and reset `PYTHONBREAKPOINT: ""` when done.
8 changes: 0 additions & 8 deletions template/k8s/base/app.configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ data:
POSTGRES_DB: "{{ copier__project_slug }}"
POSTGRES_USER: "{{ copier__project_slug }}"
REDIS_URL: "redis://redis:6379/1"
# todo: see if PYDEVD_USE_* can be removed later, I was getting this error
# with pydevd-pycharm==243.26053.29:
# cannot import name 'trace_dispatch' from '_pydevd_bundle.pydevd_trace_dispatch'
# (/app/lib/python3.12/site-packages/_pydevd_bundle/pydevd_trace_dispatch.py)
# Also probably need to add cython to local.in in that case.
PYDEVD_USE_CYTHON: "NO"
PYDEVD_USE_FRAME_EVAL: "NO"
PYTHONBREAKPOINT: "" # "utils.pycharm_debugger" for pycharm
{%- if copier__mail_service == 'Mailgun' %}
MAILGUN_DOMAIN: "{{ copier__domain_name }}"
MAILGUN_API_URL: "https://api.mailgun.net/v3"{%- endif %}
Expand Down
20 changes: 20 additions & 0 deletions template/k8s/local/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,29 @@ patches:
value:
- name: DOCKER_GATEWAY_IP # used for debugger to reach out to host machine
value: "172.17.0.1"
- op: add
path: /spec/template/spec/containers/0/ports/-
value:
name: debugpy
containerPort: 5678
target:
kind: Deployment
name: backend
# Debugger config is local-only so it can never reach sandbox/prod.
# To debug, set PYTHONBREAKPOINT below; leave it "" to disable.
- patch: |-
- op: add
path: /data/PYTHONBREAKPOINT
value: "" # "utils.pycharm_debugger" for pycharm, "{{ copier__project_slug }}.utils.vscode_debugger" for vscode
- op: add
path: /data/PYDEVD_USE_CYTHON
value: "NO"
- op: add
path: /data/PYDEVD_USE_FRAME_EVAL
value: "NO"
target:
kind: ConfigMap
name: app-config
{%- if copier__use_celery %}
- patch: |-
- op: add
Expand Down
Loading