Move name and description to class attributes on ColumnType

Instead of passing name= and description= as constructor arguments,
define them as class attributes on each subclass. This better reflects
that they are intrinsic to the type, not configurable per-instance.

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3
This commit is contained in:
Claude 2026-03-17 05:00:57 +00:00
commit 8af98c24c2
No known key found for this signature in database
4 changed files with 40 additions and 27 deletions

View file

@ -1,19 +1,17 @@
from dataclasses import dataclass
@dataclass(frozen=True, kw_only=True)
class ColumnType:
name: str
"""
Unique identifier string. Lowercase, no spaces.
Examples: "markdown", "file", "email", "url", "point", "image".
Base class for column types.
Subclasses must define ``name`` and ``description`` as class attributes:
- ``name``: Unique identifier string. Lowercase, no spaces.
Examples: "markdown", "file", "email", "url", "point", "image".
- ``description``: Human-readable label for admin UI dropdowns.
Examples: "Markdown text", "File reference", "Email address".
"""
name: str
description: str
"""
Human-readable label for admin UI dropdowns.
Examples: "Markdown text", "File reference", "Email address".
"""
async def render_cell(
self, value, column, table, database, datasette, request, config

View file

@ -8,6 +8,8 @@ from datasette.column_types import ColumnType
class UrlColumnType(ColumnType):
name = "url"
description = "URL"
async def render_cell(
self, value, column, table, database, datasette, request, config
@ -28,6 +30,8 @@ class UrlColumnType(ColumnType):
class EmailColumnType(ColumnType):
name = "email"
description = "Email address"
async def render_cell(
self, value, column, table, database, datasette, request, config
@ -48,6 +52,8 @@ class EmailColumnType(ColumnType):
class JsonColumnType(ColumnType):
name = "json"
description = "JSON data"
async def render_cell(
self, value, column, table, database, datasette, request, config
@ -76,7 +82,7 @@ class JsonColumnType(ColumnType):
@hookimpl
def register_column_types(datasette):
return [
UrlColumnType(name="url", description="URL"),
EmailColumnType(name="email", description="Email address"),
JsonColumnType(name="json", description="JSON data"),
UrlColumnType(),
EmailColumnType(),
JsonColumnType(),
]

View file

@ -1012,6 +1012,9 @@ Return a list of :ref:`ColumnType <column_types>` instances to register custom c
class ColorColumnType(ColumnType):
name = "color"
description = "CSS color value"
async def render_cell(
self,
value,
@ -1045,14 +1048,9 @@ Return a list of :ref:`ColumnType <column_types>` instances to register custom c
@hookimpl
def register_column_types(datasette):
return [
ColorColumnType(
name="color",
description="CSS color value",
)
]
return [ColorColumnType()]
Each ``ColumnType`` instance has the following attributes:
Each ``ColumnType`` subclass must define the following class attributes:
``name`` - string
Unique identifier for the column type, e.g. ``"color"``. Must be unique across all plugins.

View file

@ -352,7 +352,11 @@ async def test_validation_allows_empty_string(ds_ct):
@pytest.mark.asyncio
async def test_column_type_base_defaults():
ct = ColumnType(name="test", description="Test type")
class TestType(ColumnType):
name = "test"
description = "Test type"
ct = TestType()
assert await ct.render_cell("val", "col", "tbl", "db", None, None, None) is None
assert await ct.validate("val", None, None) is None
assert await ct.transform_value("val", None, None) == "val"
@ -378,6 +382,9 @@ async def test_render_cell_extra_with_column_types(ds_ct):
@pytest.mark.asyncio
async def test_duplicate_column_type_name_raises_error():
class DuplicateUrlType(ColumnType):
name = "url"
description = "Duplicate URL"
async def render_cell(
self, value, column, table, database, datasette, request, config
):
@ -386,7 +393,7 @@ async def test_duplicate_column_type_name_raises_error():
class _Plugin:
@hookimpl
def register_column_types(self, datasette):
return [DuplicateUrlType(name="url", description="Duplicate URL")]
return [DuplicateUrlType()]
plugin = _Plugin()
pm.register(plugin, name="test_duplicate_ct")
@ -420,6 +427,9 @@ async def test_transform_value_in_json_output(tmp_path_factory):
"""A column type with transform_value should modify rows in JSON API."""
class UpperColumnType(ColumnType):
name = "upper"
description = "Uppercase"
async def transform_value(self, value, config, datasette):
if isinstance(value, str):
return value.upper()
@ -428,7 +438,7 @@ async def test_transform_value_in_json_output(tmp_path_factory):
class _Plugin:
@hookimpl
def register_column_types(self, datasette):
return [UpperColumnType(name="upper", description="Uppercase")]
return [UpperColumnType()]
plugin = _Plugin()
pm.register(plugin, name="test_transform_ct")
@ -469,6 +479,9 @@ async def test_column_type_render_cell_has_priority_over_plugins(tmp_path_factor
"""Column type render_cell should take priority over render_cell plugin hook."""
class PriorityColumnType(ColumnType):
name = "priority_test"
description = "Priority test"
async def render_cell(
self, value, column, table, database, datasette, request, config
):
@ -481,9 +494,7 @@ async def test_column_type_render_cell_has_priority_over_plugins(tmp_path_factor
class _ColumnTypePlugin:
@hookimpl
def register_column_types(self, datasette):
return [
PriorityColumnType(name="priority_test", description="Priority test")
]
return [PriorityColumnType()]
class _RenderCellPlugin:
@hookimpl