mirror of
https://github.com/simonw/datasette.git
synced 2026-07-09 00:54:35 +02:00
Add column_details JSON extra
This commit is contained in:
parent
3b24c88e93
commit
8856914be8
5 changed files with 156 additions and 1 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import itertools
|
||||
from dataclasses import dataclass
|
||||
|
||||
from datasette.column_types import SQLiteType
|
||||
from datasette.database import QueryInterrupted
|
||||
from datasette.extras import Extra, ExtraExample, ExtraRegistry, ExtraScope, Provider
|
||||
from datasette.plugins import pm
|
||||
|
|
@ -342,6 +343,41 @@ class PrimaryKeysExtra(Extra):
|
|||
return context.pks
|
||||
|
||||
|
||||
def column_detail_as_json(column):
|
||||
return {
|
||||
"type": column.type,
|
||||
"sqlite_type": SQLiteType.from_declared_type(column.type).value,
|
||||
"notnull": column.notnull,
|
||||
"default": column.default_value,
|
||||
"is_pk": bool(column.is_pk),
|
||||
"hidden": bool(column.hidden),
|
||||
}
|
||||
|
||||
|
||||
class ColumnDetailsExtra(Extra):
|
||||
description = (
|
||||
"SQLite schema details for columns in this table. The dictionary maps "
|
||||
"column names to ``type``, ``sqlite_type``, ``notnull``, ``default``, "
|
||||
"``is_pk`` and ``hidden`` values."
|
||||
)
|
||||
example = ExtraExample("/fixtures/binary_data.json?_size=0&_extra=column_details")
|
||||
examples = {
|
||||
ExtraScope.ROW: ExtraExample(
|
||||
"/fixtures/binary_data/1.json?_extra=column_details"
|
||||
)
|
||||
}
|
||||
scopes = {ExtraScope.TABLE, ExtraScope.ROW}
|
||||
|
||||
async def resolve(self, context):
|
||||
column_details = await context.datasette._get_resource_column_details(
|
||||
context.database_name, context.table_name
|
||||
)
|
||||
return {
|
||||
column_name: column_detail_as_json(column)
|
||||
for column_name, column in column_details.items()
|
||||
}
|
||||
|
||||
|
||||
class ActionsExtra(Extra):
|
||||
description = 'Async callable returning table or view actions made available by core and plugin hooks. Each item is either a link with ``href``, ``label`` and optional ``description`` keys, or a button with ``type: "button"``, ``label``, optional ``description`` and optional ``attrs``. See :ref:`plugin_actions`, :ref:`plugin_hook_table_actions` and :ref:`plugin_hook_view_actions`.'
|
||||
scopes = {ExtraScope.TABLE}
|
||||
|
|
@ -1206,6 +1242,7 @@ TABLE_EXTRA_CLASSES = [
|
|||
ColumnsExtra,
|
||||
AllColumnsExtra,
|
||||
PrimaryKeysExtra,
|
||||
ColumnDetailsExtra,
|
||||
DisplayColumnsAndRowsProvider,
|
||||
DisplayColumnsExtra,
|
||||
DisplayRowsExtra,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
.. _v1_0_a36:
|
||||
|
||||
1.0a36 (in development)
|
||||
-----------------------
|
||||
|
||||
- The table and row JSON APIs now support ``?_extra=column_details`` for returning SQLite schema details for columns, including declared type, SQLite affinity, primary key, ``NOT NULL``, default and hidden-column metadata.
|
||||
|
||||
.. _v1_0_a35:
|
||||
|
||||
1.0a35 (2026-06-23)
|
||||
|
|
|
|||
|
|
@ -402,6 +402,24 @@ The available table extras are listed below.
|
|||
"pk"
|
||||
]
|
||||
|
||||
``column_details``
|
||||
SQLite schema details for columns in this table. The dictionary maps column names to ``type``, ``sqlite_type``, ``notnull``, ``default``, ``is_pk`` and ``hidden`` values.
|
||||
|
||||
``GET /fixtures/binary_data.json?_size=0&_extra=column_details``
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"data": {
|
||||
"type": "BLOB",
|
||||
"sqlite_type": "BLOB",
|
||||
"notnull": 0,
|
||||
"default": null,
|
||||
"is_pk": false,
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
|
||||
``display_columns``
|
||||
Column metadata used by the HTML table display. Each item includes ``name``, ``sortable``, ``is_pk``, ``type``, ``notnull``, ``description``, ``column_type`` and ``column_type_config`` keys.
|
||||
|
||||
|
|
@ -807,6 +825,24 @@ The following extras are available for row JSON responses.
|
|||
"id"
|
||||
]
|
||||
|
||||
``column_details``
|
||||
SQLite schema details for columns in this table. The dictionary maps column names to ``type``, ``sqlite_type``, ``notnull``, ``default``, ``is_pk`` and ``hidden`` values.
|
||||
|
||||
``GET /fixtures/binary_data/1.json?_extra=column_details``
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"data": {
|
||||
"type": "BLOB",
|
||||
"sqlite_type": "BLOB",
|
||||
"notnull": 0,
|
||||
"default": null,
|
||||
"is_pk": false,
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
|
||||
``render_cell``
|
||||
Rendered HTML for each cell using the render_cell plugin hook (See the :ref:`render_cell() plugin hook <plugin_hook_render_cell>` documentation.)
|
||||
|
||||
|
|
|
|||
|
|
@ -429,7 +429,7 @@ async def test_row_foreign_key_tables(ds_client):
|
|||
@pytest.mark.asyncio
|
||||
async def test_row_extras(ds_client):
|
||||
response = await ds_client.get(
|
||||
"/fixtures/simple_primary_key/1.json?_extra=database,table,primary_keys,query,request,debug,foreign_key_tables"
|
||||
"/fixtures/simple_primary_key/1.json?_extra=database,table,primary_keys,query,request,debug,foreign_key_tables,column_details"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
|
@ -446,6 +446,40 @@ async def test_row_extras(ds_client):
|
|||
"format": "json",
|
||||
}
|
||||
assert len(data["foreign_key_tables"]) == 5
|
||||
assert data["column_details"]["id"] == {
|
||||
"type": "INTEGER",
|
||||
"sqlite_type": "INTEGER",
|
||||
"notnull": 0,
|
||||
"default": None,
|
||||
"is_pk": True,
|
||||
"hidden": False,
|
||||
}
|
||||
assert data["column_details"]["content"] == {
|
||||
"type": "TEXT",
|
||||
"sqlite_type": "TEXT",
|
||||
"notnull": 0,
|
||||
"default": None,
|
||||
"is_pk": False,
|
||||
"hidden": False,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_column_details_extra_row_for_null_blob(ds_client):
|
||||
response = await ds_client.get(
|
||||
"/fixtures/binary_data/3.json?_extra=column_details"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["column_details"] == {
|
||||
"data": {
|
||||
"type": "BLOB",
|
||||
"sqlite_type": "BLOB",
|
||||
"notnull": 0,
|
||||
"default": None,
|
||||
"is_pk": False,
|
||||
"hidden": False,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -1336,6 +1336,47 @@ async def test_binary_data_in_json(ds_client, path, expected_json, expected_text
|
|||
assert response.text == expected_text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_column_details_extra_table(ds_client):
|
||||
response = await ds_client.get(
|
||||
"/fixtures/binary_data.json?_size=0&_extra=column_details"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["column_details"] == {
|
||||
"data": {
|
||||
"type": "BLOB",
|
||||
"sqlite_type": "BLOB",
|
||||
"notnull": 0,
|
||||
"default": None,
|
||||
"is_pk": False,
|
||||
"hidden": False,
|
||||
}
|
||||
}
|
||||
|
||||
response = await ds_client.get(
|
||||
"/fixtures/simple_primary_key.json?_size=0&_extra=column_details"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["column_details"] == {
|
||||
"id": {
|
||||
"type": "INTEGER",
|
||||
"sqlite_type": "INTEGER",
|
||||
"notnull": 0,
|
||||
"default": None,
|
||||
"is_pk": True,
|
||||
"hidden": False,
|
||||
},
|
||||
"content": {
|
||||
"type": "TEXT",
|
||||
"sqlite_type": "TEXT",
|
||||
"notnull": 0,
|
||||
"default": None,
|
||||
"is_pk": False,
|
||||
"hidden": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"qs",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue