Skip to content

Commit

Permalink
Prototype of rst_docs_for_dataclass mechanism, refs #1510
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Jun 28, 2023
1 parent 0072940 commit 6822378
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 1 deletion.
97 changes: 97 additions & 0 deletions datasette/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from typing import List, Dict, Optional, Any
from dataclasses import dataclass, field


def doc(documentation):
return field(metadata={"doc": documentation})


def is_builtin_type(obj):
return isinstance(
obj,
tuple(
x.__class__
for x in (int, float, str, bool, bytes, list, tuple, dict, set, frozenset)
),
)


def rst_docs_for_dataclass(klass: Any) -> str:
"""Generate reStructuredText (reST) docs for a dataclass."""
docs = []

# Class name and docstring
docs.append(klass.__name__)
docs.append("-" * len(klass.__name__))
docs.append("")
if klass.__doc__:
docs.append(klass.__doc__)
docs.append("")

# Dataclass fields
docs.append("Fields")
docs.append("~~~~~~")
docs.append("")

for name, field_info in klass.__dataclass_fields__.items():
if is_builtin_type(field_info.type):
# <class 'int'>
type_name = field_info.type.__name__
else:
# List[str]
type_name = str(field_info.type).replace("typing.", "")
docs.append(f':{name} - ``{type_name}``: {field_info.metadata.get("doc", "")}')

return "\n".join(docs)


@dataclass
class ForeignKey:
incoming: List[Dict]
outgoing: List[Dict]


@dataclass
class Table:
"A table is a useful thing"
name: str = doc("The name of the table")
columns: List[str] = doc("List of column names in the table")
primary_keys: List[str] = doc("List of column names that are primary keys")
count: int = doc("Number of rows in the table")
hidden: bool = doc(
"Should this table default to being hidden in the main database UI?"
)
fts_table: Optional[str] = doc(
"If this table has FTS support, the accompanying FTS table name"
)
foreign_keys: ForeignKey = doc("List of foreign keys for this table")
private: bool = doc("Private tables are not visible to signed-out anonymous users")


@dataclass
class View:
name: str
private: bool


@dataclass
class Query:
title: str
sql: str
name: str
private: bool


@dataclass
class Database:
content: str
private: bool
path: str
size: int
tables: List[Table]
hidden_count: int
views: List[View]
queries: List[Query]
allow_execute_sql: bool
table_columns: Dict[str, List[str]]
query_ms: float
7 changes: 6 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ["sphinx.ext.extlinks", "sphinx.ext.autodoc", "sphinx_copybutton"]
extensions = [
"sphinx.ext.extlinks",
"sphinx.ext.autodoc",
"sphinx_copybutton",
"jsoncontext",
]

extlinks = {
"issue": ("https://github.com/simonw/datasette/issues/%s", "#%s"),
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Contents
settings
introspection
custom_templates
template_context
plugins
writing_plugins
plugin_hooks
Expand Down
28 changes: 28 additions & 0 deletions docs/jsoncontext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from docutils import nodes

This comment has been minimized.

Copy link
@simonw

simonw Jun 28, 2023

Author Owner

I think this file was unnecessary for the cog prototype - it's from an earlier failed attempt I made at doing a Sphinx directive instead.

from sphinx.util.docutils import SphinxDirective
from importlib import import_module
import json


class JSONContextDirective(SphinxDirective):
required_arguments = 1

def run(self):
module_path, class_name = self.arguments[0].rsplit(".", 1)
try:
module = import_module(module_path)
dataclass = getattr(module, class_name)
except ImportError:
warning = f"Unable to import {self.arguments[0]}"
return [nodes.error(None, nodes.paragraph(text=warning))]

doc = json.dumps(
dataclass.__annotations__, indent=4, sort_keys=True, default=repr
)
doc_node = nodes.literal_block(text=doc)

return [doc_node]


def setup(app):
app.add_directive("jsoncontext", JSONContextDirective)
29 changes: 29 additions & 0 deletions docs/template_context.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.. _template_context:

Template context
================

This page describes the variables made available to templates used by Datasette to render different pages of the application.


.. [[[cog
from datasette.context import rst_docs_for_dataclass, Table
cog.out(rst_docs_for_dataclass(Table))
.. ]]]
Table
-----

A table is a useful thing

Fields
~~~~~~

:name - ``str``: The name of the table
:columns - ``List[str]``: List of column names in the table
:primary_keys - ``List[str]``: List of column names that are primary keys
:count - ``int``: Number of rows in the table
:hidden - ``bool``: Should this table default to being hidden in the main database UI?
:fts_table - ``Optional[str]``: If this table has FTS support, the accompanying FTS table name
:foreign_keys - ``ForeignKey``: List of foreign keys for this table
:private - ``bool``: Private tables are not visible to signed-out anonymous users
.. [[[end]]]

0 comments on commit 6822378

Please sign in to comment.