Skip to content

Add column types system, refs #2664#2667

Merged
simonw merged 14 commits intomainfrom
claude/column-types-system-en8tU
Mar 17, 2026
Merged

Add column types system, refs #2664#2667
simonw merged 14 commits intomainfrom
claude/column-types-system-en8tU

Conversation

@simonw
Copy link
Owner

@simonw simonw commented Mar 17, 2026

Implements the column types feature that lets Datasette and plugins annotate
columns with semantic types beyond SQLite storage types (e.g. markdown, email,
url, json, file, point). This enables type-appropriate rendering, validation,
form widgets, and API behavior.

Key changes:

  • New column_types internal DB table for storing assignments
  • ColumnType dataclass in datasette/column_types.py with render_cell,
    validate, and transform_value methods
  • register_column_types plugin hook for registering types
  • Built-in url, email, and json column types
  • Datasette API methods: get/set/remove_column_type(s),
    get_column_type_class
  • Config loading from datasette.json column_types table config key
  • column_types extra on the table JSON endpoint
  • Column type info in display_columns extra
  • Column type render_cell gets priority in rendering pipeline
  • column_type/column_type_config args added to render_cell hookspec
  • Write-path validation on insert and update

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3


📚 Documentation preview 📚: https://datasette--2667.org.readthedocs.build/en/2667/

claude added 7 commits March 17, 2026 02:40
Implements the column types feature that lets Datasette and plugins annotate
columns with semantic types beyond SQLite storage types (e.g. markdown, email,
url, json, file, point). This enables type-appropriate rendering, validation,
form widgets, and API behavior.

Key changes:
- New `column_types` internal DB table for storing assignments
- `ColumnType` dataclass in datasette/column_types.py with render_cell,
  validate, and transform_value methods
- `register_column_types` plugin hook for registering types
- Built-in url, email, and json column types
- Datasette API methods: get/set/remove_column_type(s),
  get_column_type_class
- Config loading from datasette.json `column_types` table config key
- `column_types` extra on the table JSON endpoint
- Column type info in display_columns extra
- Column type render_cell gets priority in rendering pipeline
- column_type/column_type_config args added to render_cell hookspec
- Write-path validation on insert and update

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3
- Add transform_value integration in table JSON endpoint rows
- Add tests for: duplicate type name error, row endpoint rendering,
  transform_value in JSON output, column type priority over plugins,
  row detail HTML rendering, table HTML rendering, upsert validation,
  unknown type warning logging, config overwrite on restart, and
  no-config edge case
- Total: 34 column type tests, all passing

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3
SQLite allows NULLs in primary key columns by default, so mark
database_name, resource_name, and column_name as NOT NULL explicitly.

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3
- Add register_column_types(datasette) hook documentation with example
- Update render_cell signature to include column_type and
  column_type_config parameters
- Fixes test_plugin_hooks_are_documented

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3
Add documentation for get_column_type, get_column_types,
set_column_type, remove_column_type, and get_column_type_class
methods on the Datasette instance.

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3
@codecov
Copy link

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 93.68421% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.59%. Comparing base (7f93353) to head (b7578a4).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
datasette/default_column_types.py 82.25% 11 Missing ⚠️
datasette/app.py 97.72% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2667      +/-   ##
==========================================
+ Coverage   90.50%   90.59%   +0.09%     
==========================================
  Files          53       55       +2     
  Lines        8132     8308     +176     
==========================================
+ Hits         7360     7527     +167     
- Misses        772      781       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +1208 to +1209
column_type=None,
column_type_config=None,
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are None because here we are displaying rows in an arbitrary SQL query so we can't (easily) look up column types.

Comment on lines +216 to +218
else:
col_dict["column_type"] = None
col_dict["column_type_config"] = None
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving this into the original col_dict = {...

Set column_type and column_type_config to None in the initial
col_dict instead of using an else branch.

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3
Comment on lines +79 to +81
UrlColumnType(name="url", description="URL"),
EmailColumnType(name="email", description="Email address"),
JsonColumnType(name="json", description="JSON data"),
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like how we have to pass name= and description= here, I want those on the classes.

Comment on lines +925 to +931
Returns a ``(column_type_name, config)`` tuple for the specified column. ``column_type_name`` is a string like ``"email"`` or ``"url"``, and ``config`` is a dict or ``None``. If no column type is assigned, returns ``(None, None)``.

.. code-block:: python

ct_name, config = await datasette.get_column_type(
"mydb", "mytable", "email_col"
)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the return type of this is right. Can we return an instantiated ColumnType class instead perhaps?

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
register_column_types(datasette)
--------------------------------

Return a list of :ref:`ColumnType <column_types>` instances to register custom column types. Column types define how values in specific columns are rendered, validated, and transformed.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should return a list of classes, not instances. We can then use instances for column types that have had their config attached to them.

claude added 4 commits March 17, 2026 05:18
- register_column_types() now returns classes instead of instances
- ColumnType.__init__ takes optional config=, baking it into the instance
- get_column_type() returns a ColumnType instance (or None) instead of a
  (name, config) tuple
- get_column_types() returns {col: ColumnType instance} instead of tuples
- Remove get_column_type_class() - no longer needed
- render_cell/validate/transform_value methods no longer take config arg;
  use self.config instead
- render_cell hook takes column_type (ColumnType or None) instead of
  column_type + column_type_config

https://claude.ai/code/session_01SvPEPqHgURTWESRp28pTC3

ct = TestType(config={"key": "value"})
assert ct.config == {"key": "value"}
assert ct.name == "test"
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointless test.

@simonw simonw changed the title Add column types system for semantic column annotations Add column types system, refs #2664 Mar 17, 2026
@simonw simonw linked an issue Mar 17, 2026 that may be closed by this pull request
@simonw simonw merged commit bc7a19b into main Mar 17, 2026
39 checks passed
@simonw simonw deleted the claude/column-types-system-en8tU branch March 17, 2026 05:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Column types mechanism in Datasette core

2 participants