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
7 changes: 0 additions & 7 deletions .flake8

This file was deleted.

2 changes: 0 additions & 2 deletions .isort.cfg

This file was deleted.

4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ When `manage.py runserver` runs, `reactivated/__init__.py` patches the process:
- **syrupy**: Snapshot testing (snapshots in `tests/__snapshots__/`)
- **Mypy plugin**: `reactivated/plugin.py` — hooks for `Pick` type analysis and `@template`/`@interface` decorator typing

## CI

When monitoring CI after a push, only wait for "Code tests" and the ubuntu integration test to pass. The macOS integration tests (macos-14, macos-15) are slow and verified manually — don't block on them.

## Environment

Uses Nix (`shell.nix`) for dependency management. Key env vars:
Expand Down
13 changes: 0 additions & 13 deletions development/.flake8

This file was deleted.

2 changes: 0 additions & 2 deletions development/.isort.cfg

This file was deleted.

1 change: 1 addition & 0 deletions development/manage.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""

import os
import sys

Expand Down
12 changes: 8 additions & 4 deletions development/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ requires-python = ">=3.12"
dependencies = [
"Django==5.0.2",
"psycopg[binary]==3.1.18",
"flake8==7.0.0",
"isort==5.13.2",
"black==22.3.0",
"autoflake==2.3.0",
"ruff>=0.11",
"pytest==8.0.1",
"pytest-django==4.5.2",
"gunicorn==22.0.0",
Expand All @@ -18,3 +15,10 @@ dependencies = [

[tool.uv.sources]
reactivated = { path = "..", editable = true }

[tool.ruff.lint]
ignore = [
"E501", # Line too long: formatter handles this.
"F403", # Wildcard imports: mypy analyzes these.
"F821", # Undefined names: used in Pick literals.
]
7 changes: 2 additions & 5 deletions development/scripts/fix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,10 @@ cd "$PROJECT_ROOT"

if [[ -n "${CHANGED_PY_FILES// /}" ]]; then
# shellcheck disable=SC2086
autoflake --exclude node_modules,.venv -i -r --remove-all-unused-imports $CHANGED_PY_FILES
ruff check --force-exclude --fix $CHANGED_PY_FILES || true

# shellcheck disable=SC2086
isort $CHANGED_PY_FILES

# shellcheck disable=SC2086
black $CHANGED_PY_FILES
ruff format --force-exclude $CHANGED_PY_FILES
fi

if [[ -n "${CHANGED_TS_JS_FILES// /}" ]]; then
Expand Down
5 changes: 2 additions & 3 deletions development/scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,8 @@ cd "$PROJECT_ROOT"

if [[ $SERVER -eq 1 ]]; then
capture_stdout_and_stderr_if_successful pytest
capture_stdout_and_stderr_if_successful flake8 .
capture_stdout_and_stderr_if_successful isort -c .
capture_stdout_and_stderr_if_successful black . --check
capture_stdout_and_stderr_if_successful ruff check --force-exclude .
capture_stdout_and_stderr_if_successful ruff format --force-exclude --check .
capture_stdout_and_stderr_if_successful mypy --no-incremental .
capture_stdout_and_stderr_if_successful python manage.py makemigrations --dry-run --check
fi
Expand Down
1 change: 0 additions & 1 deletion development/server/example/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Any

from django import forms

from reactivated.forms import ModelFormSetFactory

from . import models
Expand Down
1 change: 0 additions & 1 deletion development/server/example/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@


class Migration(migrations.Migration):

initial = True

dependencies = []
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# Generated by Django 5.0.2 on 2026-03-21 21:00

import server.example.models
from django.db import migrations

import reactivated.constraints
import reactivated.fields
from django.db import migrations

import server.example.models

class Migration(migrations.Migration):

class Migration(migrations.Migration):
dependencies = [
("example", "0001_initial"),
]
Expand Down
1 change: 0 additions & 1 deletion development/server/example/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import enum

from django.db import models

from reactivated.fields import EnumField


Expand Down
18 changes: 14 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,8 @@ dev = [
"django-extensions==3.2.3",
"django-stubs-ext==5.1.1",
"djangorestframework==3.14.0",
"flake8==7.1.1",
"greenlet==3.1.1",
"isort==5.13.2",
"black==24.10.0",
"autoflake==2.3.1",
"ruff>=0.11",
"jsonschema==3.1.1",
"Jinja2==3.1.4",
"playwright==1.48.0",
Expand Down Expand Up @@ -59,3 +56,16 @@ include = ["reactivated*"]

[tool.setuptools.package-data]
reactivated = ["py.typed", "templates/**/*"]

[tool.ruff.lint]
ignore = [
"E501", # Line too long: formatter handles this.
"F405", # May be undefined from star import.
"F403", # Wildcard imports: mypy analyzes these.
"E731", # Do not assign a lambda.
"F821", # Undefined names: used in Pick literals.
"E721", # Type comparison: used in generic alias origin checks.
]

[tool.ruff.lint.isort]
known-first-party = ["reactivated"]
4 changes: 3 additions & 1 deletion reactivated/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,9 @@ def ssr(
]: ...


def ssr(*, props: type[P], params: type[K] | None = None) -> (
def ssr(
*, props: type[P], params: type[K] | None = None
) -> (
Callable[
[NoArgsView[P]],
Callable[[Arg(HttpRequest, "request"), KwArg(Any)], HttpResponse],
Expand Down
6 changes: 3 additions & 3 deletions reactivated/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ def render(self, context: Any = None, request: HttpRequest | None = None) -> str

return render_jsx_to_string(request, serialized_context, props)

assert (
False
), "At this time, only templates with the request object can be rendered with reactivated"
assert False, (
"At this time, only templates with the request object can be rendered with reactivated"
)


class AdapterTemplate(JSXTemplate):
Expand Down
8 changes: 6 additions & 2 deletions reactivated/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def constraint_sql(
columns=columns,
)

def create_sql(self, model: type[Model] | None, schema_editor: BaseDatabaseSchemaEditor | None) -> Statement: # type: ignore[override]
def create_sql( # type: ignore[override]
self, model: type[Model] | None, schema_editor: BaseDatabaseSchemaEditor | None
) -> Statement:
assert model is not None
assert schema_editor is not None

Expand All @@ -64,7 +66,9 @@ def create_sql(self, model: type[Model] | None, schema_editor: BaseDatabaseSchem
columns=", ".join([f"'{column}'" for column in columns]),
)

def remove_sql(self, model: type[Model] | None, schema_editor: BaseDatabaseSchemaEditor | None) -> Statement: # type: ignore[override]
def remove_sql( # type: ignore[override]
self, model: type[Model] | None, schema_editor: BaseDatabaseSchemaEditor | None
) -> Statement:
assert model is not None
assert schema_editor is not None

Expand Down
4 changes: 3 additions & 1 deletion reactivated/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ def deconstruct(self) -> Any:
del kwargs["choices"]
return name, path, args, kwargs

def contribute_to_class(self, cls: type[models.Model], name: str, **kwargs: Any) -> None: # type: ignore[override]
def contribute_to_class( # type: ignore[override]
self, cls: type[models.Model], name: str, **kwargs: Any
) -> None:
"""
We don't store the enum in the constraint. Instead, we store the fields
so the autodetection for changed enums works automatically.
Expand Down
2 changes: 1 addition & 1 deletion reactivated/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def computed_foreign_key(
]
):
def inner(
fget: Callable[[T], SOptionalInstance]
fget: Callable[[T], SOptionalInstance],
) -> ComputedRelation[T, S, SOptionalInstance]:
return ComputedRelation(
fget=fget, label=label, model=model, many=False, null=null
Expand Down
16 changes: 9 additions & 7 deletions reactivated/pick.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ def get_field_descriptor(
):
if len(remaining) == 0:
if isinstance(field_descriptor.descriptor, ForeignObjectRel):
assert (
False
), f"You cannot Pick reverse relationships. Specify which fields from {field_name} you want, such as {field_name}.example_field"
assert False, (
f"You cannot Pick reverse relationships. Specify which fields from {field_name} you want, such as {field_name}.example_field"
)

if (
isinstance(field_descriptor.descriptor, (models.ForeignKey))
Expand All @@ -118,12 +118,14 @@ def get_field_descriptor(
(),
)

assert (
False
), f"Do not specify related fields directly for model {model_class} in {pick_name}. Use {field_name}_id if you just want the reference or {field_name}.subfield if you want fields inside the related model"
assert False, (
f"Do not specify related fields directly for model {model_class} in {pick_name}. Use {field_name}_id if you just want the reference or {field_name}.subfield if you want fields inside the related model"
)

nested_descriptor, nested_field_names = get_field_descriptor(
pick_name, field_descriptor.annotation or field_descriptor.descriptor.related_model, remaining # type: ignore[arg-type]
pick_name,
field_descriptor.annotation or field_descriptor.descriptor.related_model, # type: ignore[arg-type]
remaining,
)

# TODO: Maybe RelatedField replaces all of the above?
Expand Down
6 changes: 4 additions & 2 deletions reactivated/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def analyze_template(ctx: ClassDefContext) -> None:
Instance(http_request.node, []), # type: ignore[union-attr, arg-type]
),
type_annotation=Instance(
http_request.node, [] # type: ignore[union-attr, arg-type]
http_request.node, # type: ignore[union-attr, arg-type]
[],
),
initializer=None,
kind=ARG_POS,
Expand All @@ -71,7 +72,8 @@ def analyze_template(ctx: ClassDefContext) -> None:
"render",
args=[request_arg],
return_type=Instance(
template_response.node, [] # type: ignore[union-attr, arg-type]
template_response.node, # type: ignore[union-attr, arg-type]
[],
),
)

Expand Down
36 changes: 21 additions & 15 deletions reactivated/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ def context(
def inner_context_provider(
request: THttpRequest, *args: Any, **kwargs: Any
) -> Any:
return context_provider(request, self.context_provider(request, *args, **kwargs)) # type: ignore[call-arg]
return context_provider(
request, self.context_provider(request, *args, **kwargs)
) # type: ignore[call-arg]

inner_context_provider.__annotations__ = self.context_provider.__annotations__
inner_context_provider.__name__ = context_provider.__name__
Expand All @@ -155,9 +157,8 @@ def process(
| Callable[[THttpRequest, None], TResponse]
),
*,
context_provider: None | (
Callable[[THttpRequest, TFirst, TSecond], TContext]
) = None,
context_provider: None
| (Callable[[THttpRequest, TFirst, TSecond], TContext]) = None,
) -> URLPattern: ...

@overload
Expand All @@ -168,9 +169,8 @@ def process(
| Callable[[THttpRequest, None, TForm], TResponse]
),
*,
context_provider: None | (
Callable[[THttpRequest, TFirst, TSecond], TContext]
) = None,
context_provider: None
| (Callable[[THttpRequest, TFirst, TSecond], TContext]) = None,
) -> URLPattern: ...

def process(
Expand All @@ -182,9 +182,8 @@ def process(
| Callable[[THttpRequest, None, TForm], TResponse]
),
*,
context_provider: None | (
Callable[[THttpRequest, TFirst, TSecond], TContext]
) = None,
context_provider: None
| (Callable[[THttpRequest, TFirst, TSecond], TContext]) = None,
) -> URLPattern:
return_type = get_type_hints(view)["return"]
return_schema = create_schema(return_type, registry.definitions_registry)
Expand Down Expand Up @@ -248,9 +247,12 @@ def wrapped_view(request: THttpRequest, *args: Any, **kwargs: Any) -> Any:
if request.method == "POST":
if form.is_valid():
try:
response = view(request, context, form) if form_type is not None else view(request, context) # type: ignore[arg-type, call-arg]
response = (
view(request, context, form) # type: ignore[arg-type, call-arg]
if form_type is not None
else view(request, context)
)
except forms.ValidationError as error:

if hasattr(error, "error_dict"):
return JsonResponse(
error.message_dict, status=400, safe=False
Expand Down Expand Up @@ -302,7 +304,9 @@ def wrapped_view(request: THttpRequest, *args: Any, **kwargs: Any) -> Any:
"type": (
"form_group"
if issubclass(form_class, FormGroup)
else "form" if issubclass(form_class, forms.BaseForm) else "form_set"
else "form"
if issubclass(form_class, forms.BaseForm)
else "form_set"
),
}
route = path(url_path, wrapped_view, name=url_name)
Expand Down Expand Up @@ -480,7 +484,9 @@ def wrapped_view(request: THttpRequest, *args: Any, **kwargs: Any) -> Any:
"type": (
"form_group"
if issubclass(form_class, FormGroup)
else "form" if issubclass(form_class, forms.BaseForm) else "form_set"
else "form"
if issubclass(form_class, forms.BaseForm)
else "form_set"
),
}
route = path(url_path, wrapped_view, name=url_name)
Expand All @@ -506,6 +512,6 @@ def context(


def create_rpc(
authentication: Callable[[HttpRequest], THttpRequest | enum.Enum | Literal[False]]
authentication: Callable[[HttpRequest], THttpRequest | enum.Enum | Literal[False]],
) -> RPC[THttpRequest]:
return RPC[THttpRequest](authentication)
Loading
Loading