diff --git a/datasette/column_types.py b/datasette/column_types.py index 240bcc8f..e5b54845 100644 --- a/datasette/column_types.py +++ b/datasette/column_types.py @@ -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 diff --git a/datasette/default_column_types.py b/datasette/default_column_types.py index 24e761ba..87d9713d 100644 --- a/datasette/default_column_types.py +++ b/datasette/default_column_types.py @@ -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(), ] diff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst index 052c17b0..bab70edf 100644 --- a/docs/plugin_hooks.rst +++ b/docs/plugin_hooks.rst @@ -1012,6 +1012,9 @@ Return a list of :ref:`ColumnType ` 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 ` 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. diff --git a/tests/test_column_types.py b/tests/test_column_types.py index 7e16e6c2..8315d603 100644 --- a/tests/test_column_types.py +++ b/tests/test_column_types.py @@ -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