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
22 changes: 22 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,25 @@ jobs:
working-directory: packages/runtime-sdk
run: |
uv run --frozen pytest -v --color=yes tests

django-test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ['3.14']
runs-on: ${{ matrix.os }}
timeout-minutes: 30

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: ${{ matrix.python-version }}

- name: Run django-cf tests
working-directory: packages/django-cf
run: |
uv run --frozen pytest -v --color=yes tests
18 changes: 5 additions & 13 deletions packages/django-cf/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,14 @@ It is published to PyPI as `django-cf` and imported as `django_cf`.
- Both database backends have transactions **disabled**. Every query commits immediately and rollbacks are unavailable, so code that relies on `atomic()` for correctness will not behave as it does on other backends.
- This package still uses a flat layout (`django_cf/`) and a setuptools build backend, unlike `packages/cli` and `packages/runtime-sdk` which use src-layout and hatchling.
- `templates/d1/` and `templates/durable-objects/` are deployable example projects, and `tests/servers/r2/` is a fixture app used by the R2 integration tests. None of them are part of the wheel.
- Ruff is configured in `pyproject.toml`. `target-version` is `py312` here because the package declares `requires-python = ">=3.12"`; the two sibling packages target `py311`.
- That ruff config deliberately ignores `B904`, `B905`, `C901`, `PERF401`, `PLR0911`, `PLR0912`, `PLR0913`, `PLR0915`, `PLR2004`, `PLW0603` and `UP038`, because fixing the existing violations would mean behavioural or structural changes. Write new code that does not need those ignores.
- mypy and semgrep currently skip this package. Adding either is a deliberate follow-up, not something to switch on incidentally.

## Testing

- Lint everything with `uvx pre-commit run -a` from the repository root.
- The suites split by whether they need a real Worker:
- Host-runnable, no Node required: `tests/db/`, `tests/middleware/`, `tests/test_wsgi_handler.py`.
- Require `wrangler dev`: `tests/d1/`, `tests/durable_objects/`, `tests/r2/`, `tests/e2e/`, `tests/test_date_trunc.py`.
- A bare `pytest` collects everything and fails without a Node toolchain. For the host-runnable subset, run from `packages/django-cf`:
```bash
uv sync
uv run pytest tests/db tests/middleware tests/test_wsgi_handler.py
```
- The Worker-backed suites additionally need `npm run setup-test`, which runs `npm install` and copies `django_cf/` into each fixture's `python_modules/` directory. Adding, moving or renaming library files means re-running it.
- Those suites get their base URL from the `d1_web_server`, `durable_objects_web_server` and `r2_web_server` fixtures in `tests/utils.py`, each of which spawns `npx wrangler dev` on a free port.
- The template and fixture apps expose management endpoints for test setup, such as `/__run_migrations__/` and `/__create_admin__/`, which creates an admin user with username `admin` and password `password`.
- This package has no test job in `.github/workflows/tests.yml` yet, so nothing here runs in CI.
- Every suite needs a Node toolchain, because every suite runs against a real Worker. Run them from `packages/django-cf` with `uv run --frozen pytest tests`; the `django-test` job in `.github/workflows/tests.yml` runs the same command.
- `tests/conftest.py` is the whole harness. It copies the worker project into a tmpdir, runs `pywrangler sync`, overwrites the vendored `django_cf/` with the working tree, then starts `pywrangler dev` on a free port. Nothing is installed into the repository, so there is no setup step to re-run after editing library files.
- Two shapes of suite:
- `tests/d1/`, `tests/durable_objects/`, `tests/r2/` drive a deployed Django app over HTTP, via the session-scoped `d1_web_server`, `durable_objects_web_server` and `r2_web_server` fixtures. Those apps live in `templates/` and `tests/servers/r2/` and expose management endpoints for setup, such as `/__run_migrations__/` and `/__create_admin__/`, which creates an admin user with username `admin` and password `password`.
- `tests/in_worker/` runs pytest *inside* workerd. The real test bodies are `tests/in_worker/worker/src/test_*.py`; `register_in_worker_suites` discovers them by AST and generates one host-side test per in-worker test, so a failure inside the Worker surfaces as an ordinary pytest failure. `pyproject.toml` ignores that `src` directory so the host collector does not try to import Worker-only modules.
11 changes: 6 additions & 5 deletions packages/django-cf/django_cf/db/backends/d1/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,18 @@ def run_query(self, query, params=None) -> CFResult:
read_only = is_read_only_query(proc_query)
try:
if read_only:
response = self.run_sync(stmt.raw()).to_py()
response = self.run_sync(stmt.raw())
result = CFResult.from_object(query, params, response, len(response), 0)
else:
response = self.run_sync(stmt.all())
meta = response["meta"]
result = CFResult.from_object(
query,
params,
response.results.to_py(),
response.meta.rows_read,
response.meta.rows_written,
response.meta.last_row_id,
response["results"],
meta["rows_read"],
meta["rows_written"],
meta["last_row_id"],
)
except Exception:
from js import Error
Expand Down
8 changes: 2 additions & 6 deletions packages/django-cf/django_cf/storage/r2.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _read(self, name):
if r2_object is None:
return None

return self._run_sync(r2_object.arrayBuffer()).to_bytes()
return bytes(self._run_sync(r2_object.arrayBuffer()))
except Exception:
return None

Expand Down Expand Up @@ -174,9 +174,7 @@ def listdir(self, path):
full_path += "/"

bucket = self._get_bucket()
result = self._run_sync(
bucket.list({"prefix": full_path, "delimiter": "/"})
).to_py()
result = self._run_sync(bucket.list({"prefix": full_path, "delimiter": "/"}))

directories = []
files = []
Expand All @@ -189,8 +187,6 @@ def listdir(self, path):

objects = result.get("objects", [])
for obj in objects:
_obj = obj.to_py()

if not obj.key.endswith("/"):
files.append(os.path.basename(obj.key))

Expand Down
6 changes: 1 addition & 5 deletions packages/django-cf/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@
},
"license": "MIT",
"scripts": {
"setup-durable-objects": "cd templates/durable-objects && npm run dependencies && rm -rf python_modules/django_cf && cp -r ../../django_cf python_modules/django_cf",
"setup-d1": "cd templates/d1 && npm run dependencies && rm -rf python_modules/django_cf && cp -r ../../django_cf python_modules/django_cf",
"setup-test-servers": "cd tests/servers/r2 && npm run dependencies && rm -rf python_modules/django_cf && cp -r ../../../django_cf python_modules/django_cf",
"setup-test": "pip install -e .[dev] && npm run setup-durable-objects && npm run setup-d1 && npm run setup-test-servers",
"test": "pytest",
"test": "uv run pytest",
"upgrade-templates": "cd templates/durable-objects && uv add django-cf --upgrade && cd ../d1 && uv add django-cf --upgrade",
"build": "rm -rf dist/ && python3 -m build",
"publish": "python3 -m twine upload dist/*"
Expand Down
10 changes: 3 additions & 7 deletions packages/django-cf/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
"Programming Language :: Python :: 3.12"
]

[project.optional-dependencies]
[dependency-groups]
dev = [
"pytest",
"pytest-cov",
Expand Down Expand Up @@ -87,18 +87,14 @@ lint.ignore = [
"UP038", # `isinstance(x, X | Y)` (also ignored by runtime-sdk)
]
lint.flake8-comprehensions.allow-dict-calls-with-keyword-arguments = true
# Pytest fixtures are imported for their side effect and then shadowed by the
# test function parameter of the same name, which reads as F811 to ruff.
lint.per-file-ignores."tests/**" = ["F811"]
# tests/utils.py launches `wrangler dev` in its own process group.
lint.per-file-ignores."tests/utils.py" = ["PLW1509"]
# Generated Django scaffolding kept verbatim so it stays copy-pasteable.
lint.per-file-ignores."tests/servers/**" = ["F401"]
lint.per-file-ignores."templates/**" = ["F401"]

[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q"
# TODO: e2e tests are flaky. Enable or refactor test cases
addopts = ["-ra", "--ignore=tests/e2e", "--ignore=tests/in_worker/worker/src"]
testpaths = [
"tests",
]
Expand Down
Loading
Loading