extra_field() - Context fields documented by their Extra class

A Context dataclass field declared with extra_field() takes its
documentation from the description on the registered Extra of the same
name, validated against the class's extras_scope. This keeps doc
strings next to the resolve() code instead of duplicating them on the
dataclass, ahead of introducing TableContext and RowContext.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Simon Willison 2026-06-11 06:51:54 -07:00
commit 63995ce823
2 changed files with 91 additions and 1 deletions

View file

@ -7,21 +7,66 @@ class ContextField:
name: str
type_name: str
help: str
from_extra: bool = False
def extra_field():
"""
Declare a Context dataclass field whose value comes from a registered
Extra of the same name - its documentation is the Extra description,
so the doc string lives next to the resolve() code rather than being
duplicated on the dataclass.
"""
return dataclasses.field(metadata={"from_extra": True})
class Context:
"Base class for all documented contexts"
# Set on subclasses whose extra_field() fields should be resolved
# against the extras registry for this scope
extras_scope = None
@classmethod
def documented_fields(cls):
"List of ContextField describing the documented fields of this context"
documented = []
for f in dataclasses.fields(cls):
from_extra = bool(f.metadata.get("from_extra"))
if from_extra:
help_text = cls._extra_description(f.name)
else:
help_text = f.metadata.get("help", "")
documented.append(
ContextField(
name=f.name,
type_name=getattr(f.type, "__name__", str(f.type)),
help=f.metadata.get("help", ""),
help=help_text,
from_extra=from_extra,
)
)
return documented
@classmethod
def _extra_description(cls, name):
# Imported lazily - table_extras is not needed just to define
# Context subclasses
from datasette.views.table_extras import table_extra_registry
try:
extra_class = table_extra_registry.classes_by_name[name]
except KeyError:
raise KeyError(
"{}.{} is declared with extra_field() but there is no "
"registered extra of that name".format(cls.__name__, name)
)
if cls.extras_scope is not None and not extra_class.available_for(
cls.extras_scope
):
raise ValueError(
"{}.{} is declared with extra_field() but the {} extra is "
"not available for scope {}".format(
cls.__name__, name, name, cls.extras_scope
)
)
return extra_class.description or ""

View file

@ -44,6 +44,51 @@ def test_context_dataclass_fields_all_have_help(klass):
)
def test_extra_field_documentation_comes_from_the_extra_class():
from datasette.views import extra_field
from datasette.views.table_extras import CountExtra
@dataclass
class DemoContext(Context):
extras_scope = ExtraScope.TABLE
count: int = extra_field()
name: str = field(metadata={"help": "The name"})
fields = {f.name: f for f in DemoContext.documented_fields()}
assert fields["count"].help == CountExtra.description
assert fields["count"].from_extra
assert fields["name"].help == "The name"
assert not fields["name"].from_extra
def test_extra_field_must_match_a_registered_extra():
from datasette.views import extra_field
@dataclass
class BadContext(Context):
extras_scope = ExtraScope.TABLE
not_a_real_extra: str = extra_field()
with pytest.raises(KeyError):
BadContext.documented_fields()
def test_extra_field_must_be_available_for_the_scope():
from datasette.views import extra_field
@dataclass
class WrongScopeContext(Context):
extras_scope = ExtraScope.ROW
# count is a TABLE-scope extra, not available for ROW
count: int = extra_field()
with pytest.raises(ValueError):
WrongScopeContext.documented_fields()
@pytest.fixture
def isolate_extra_template_vars_plugins():
# Datasette instances created with plugins_dir (e.g. the session-scoped