Skip to content

Commit

Permalink
Removed units functionality and Pint dependency
Browse files Browse the repository at this point in the history
Closes #2400, unblocks #2320
  • Loading branch information
simonw authored Aug 21, 2024
1 parent d444b6a commit 39dfc7d
Show file tree
Hide file tree
Showing 14 changed files with 14 additions and 182 deletions.
1 change: 0 additions & 1 deletion datasette/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@

from .events import Event
from .views import Context
from .views.base import ureg
from .views.database import database_download, DatabaseView, TableCreateView, QueryView
from .views.index import IndexView
from .views.special import (
Expand Down
24 changes: 2 additions & 22 deletions datasette/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,12 +368,8 @@ class Filters:
)
_filters_by_key = {f.key: f for f in _filters}

def __init__(self, pairs, units=None, ureg=None):
if units is None:
units = {}
def __init__(self, pairs):
self.pairs = pairs
self.units = units
self.ureg = ureg

def lookups(self):
"""Yields (lookup, display, no_argument) pairs"""
Expand Down Expand Up @@ -413,30 +409,14 @@ def selections(self):
def has_selections(self):
return bool(self.pairs)

def convert_unit(self, column, value):
"""If the user has provided a unit in the query, convert it into the column unit, if present."""
if column not in self.units:
return value

# Try to interpret the value as a unit
value = self.ureg(value)
if isinstance(value, numbers.Number):
# It's just a bare number, assume it's the column unit
return value

column_unit = self.ureg(self.units[column])
return value.to(column_unit).magnitude

def build_where_clauses(self, table):
sql_bits = []
params = {}
i = 0
for column, lookup, value in self.selections():
filter = self._filters_by_key.get(lookup, None)
if filter:
sql_bit, param = filter.where_clause(
table, column, self.convert_unit(column, value), i
)
sql_bit, param = filter.where_clause(table, column, value, i)
sql_bits.append(sql_bit)
if param is not None:
if not isinstance(param, list):
Expand Down
1 change: 0 additions & 1 deletion datasette/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1368,7 +1368,6 @@ def recursive_move(src, dest, path=None):
"fts_table",
"fts_pk",
"searchmode",
"units",
)


Expand Down
4 changes: 0 additions & 4 deletions datasette/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from markupsafe import escape


import pint

from datasette.database import QueryInterrupted
from datasette.utils.asgi import Request
from datasette.utils import (
Expand All @@ -32,8 +30,6 @@
BadRequest,
)

ureg = pint.UnitRegistry()


class DatasetteError(Exception):
def __init__(
Expand Down
1 change: 0 additions & 1 deletion datasette/views/row.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ async def template_data():
"columns": columns,
"primary_keys": resolved.pks,
"primary_key_values": pk_values,
"units": (await self.ds.table_config(database, table)).get("units", {}),
}

if "foreign_key_tables" in (request.args.get("_extras") or "").split(","):
Expand Down
13 changes: 2 additions & 11 deletions datasette/views/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
from datasette.utils.asgi import BadRequest, Forbidden, NotFound, Response
from datasette.filters import Filters
import sqlite_utils
from .base import BaseView, DatasetteError, ureg, _error, stream_csv
from .base import BaseView, DatasetteError, _error, stream_csv
from .database import QueryView

LINK_WITH_LABEL = (
Expand Down Expand Up @@ -292,14 +292,6 @@ async def display_columns_and_rows(
),
)
)
elif column in table_config.get("units", {}) and value != "":
# Interpret units using pint
value = value * ureg(table_config["units"][column])
# Pint uses floating point which sometimes introduces errors in the compact
# representation, which we have to round off to avoid ugliness. In the vast
# majority of cases this rounding will be inconsequential. I hope.
value = round(value.to_compact(), 6)
display_value = markupsafe.Markup(f"{value:~P}".replace(" ", " "))
else:
display_value = str(value)
if truncate_cells and len(display_value) > truncate_cells:
Expand Down Expand Up @@ -1017,7 +1009,6 @@ async def table_view_data(
nofacet = True

table_metadata = await datasette.table_config(database_name, table_name)
units = table_metadata.get("units", {})

# Arguments that start with _ and don't contain a __ are
# special - things like ?_search= - and should not be
Expand All @@ -1029,7 +1020,7 @@ async def table_view_data(
filter_args.append((key, v))

# Build where clauses from query string arguments
filters = Filters(sorted(filter_args), units, ureg)
filters = Filters(sorted(filter_args))
where_clauses, params = filters.build_where_clauses(table_name)

# Execute filters_from_request plugin hooks - including the default
Expand Down
94 changes: 0 additions & 94 deletions docs/metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,100 +205,6 @@ These will be displayed at the top of the table page, and will also show in the

You can see an example of how these look at `latest.datasette.io/fixtures/roadside_attractions <https://latest.datasette.io/fixtures/roadside_attractions>`__.

Specifying units for a column
-----------------------------

Datasette supports attaching units to a column, which will be used when displaying
values from that column. SI prefixes will be used where appropriate.

Column units are configured in the metadata like so:

.. [[[cog
metadata_example(cog, {
"databases": {
"database1": {
"tables": {
"example_table": {
"units": {
"column1": "metres",
"column2": "Hz"
}
}
}
}
}
})
.. ]]]
.. tab:: metadata.yaml

.. code-block:: yaml
databases:
database1:
tables:
example_table:
units:
column1: metres
column2: Hz
.. tab:: metadata.json

.. code-block:: json
{
"databases": {
"database1": {
"tables": {
"example_table": {
"units": {
"column1": "metres",
"column2": "Hz"
}
}
}
}
}
}
.. [[[end]]]
Units are interpreted using Pint_, and you can see the full list of available units in
Pint's `unit registry`_. You can also add `custom units`_ to the metadata, which will be
registered with Pint:

.. [[[cog
metadata_example(cog, {
"custom_units": [
"decibel = [] = dB"
]
})
.. ]]]
.. tab:: metadata.yaml

.. code-block:: yaml
custom_units:
- decibel = [] = dB
.. tab:: metadata.json

.. code-block:: json
{
"custom_units": [
"decibel = [] = dB"
]
}
.. [[[end]]]
.. _Pint: https://pint.readthedocs.io/
.. _unit registry: https://github.com/hgrecco/pint/blob/master/pint/default_en.txt
.. _custom units: http://pint.readthedocs.io/en/latest/defining.html

.. _metadata_default_sort:

Setting a default sort order
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ def get_version():
"httpx>=0.20",
'importlib_resources>=1.3.1; python_version < "3.9"',
'importlib_metadata>=4.6; python_version < "3.10"',
"pint>=0.9",
"pluggy>=1.0",
"uvicorn>=0.11",
"aiofiles>=0.4",
Expand Down
11 changes: 0 additions & 11 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,6 @@ def generate_sortable_rows(num):
],
},
"no_primary_key": {"sortable_columns": [], "hidden": True},
"units": {"units": {"distance": "m", "frequency": "Hz"}},
"primary_key_multiple_columns_explicit_label": {
"label_column": "content2"
},
Expand Down Expand Up @@ -507,16 +506,6 @@ def generate_sortable_rows(num):
FOREIGN KEY ("foreign_key_with_custom_label") REFERENCES [primary_key_multiple_columns_explicit_label](id)
);
CREATE TABLE units (
pk integer primary key,
distance int,
frequency int
);
INSERT INTO units VALUES (1, 1, 100);
INSERT INTO units VALUES (2, 5000, 2500);
INSERT INTO units VALUES (3, 100000, 75000);
CREATE TABLE tags (
tag TEXT PRIMARY KEY
);
Expand Down
13 changes: 8 additions & 5 deletions tests/plugins/my_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@
from datasette.utils import path_with_added_args
from datasette.utils.asgi import asgi_send_json, Response
import base64
import pint
import json
import urllib

ureg = pint.UnitRegistry()
import urllib.parse


@hookimpl
def prepare_connection(conn, database, datasette):
def convert_units(amount, from_, to_):
"""select convert_units(100, 'm', 'ft');"""
return (amount * ureg(from_)).to(to_).to_tuple()[0]
# Convert meters to feet
if from_ == "m" and to_ == "ft":
return amount * 3.28084
# Convert feet to meters
if from_ == "ft" and to_ == "m":
return amount / 3.28084
assert False, "Unsupported conversion"

conn.create_function("convert_units", 3, convert_units)

Expand Down
12 changes: 0 additions & 12 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,16 +528,6 @@ async def test_database_page(ds_client):
},
"private": False,
},
{
"name": "units",
"columns": ["pk", "distance", "frequency"],
"primary_keys": ["pk"],
"count": 3,
"hidden": False,
"fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []},
"private": False,
},
{
"name": "no_primary_key",
"columns": ["content", "a", "b", "c"],
Expand Down Expand Up @@ -1133,7 +1123,6 @@ async def test_config_json(config, expected):
],
},
"no_primary_key": {"sortable_columns": [], "hidden": True},
"units": {"units": {"distance": "m", "frequency": "Hz"}},
"primary_key_multiple_columns_explicit_label": {
"label_column": "content2"
},
Expand Down Expand Up @@ -1168,7 +1157,6 @@ async def test_config_json(config, expected):
"text",
]
},
"units": {"units": {"distance": "m", "frequency": "Hz"}},
# These one get redacted:
"no_primary_key": "***",
"primary_key_multiple_columns_explicit_label": "***",
Expand Down
1 change: 0 additions & 1 deletion tests/test_internals_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,6 @@ async def test_table_names(db):
"table/with/slashes.csv",
"complex_foreign_keys",
"custom_foreign_key_label",
"units",
"tags",
"searchable",
"searchable_tags",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,8 @@ def extra_template_vars(view_name):
(
("/", "index"),
("/fixtures", "database"),
("/fixtures/units", "table"),
("/fixtures/units/1", "row"),
("/fixtures/facetable", "table"),
("/fixtures/facetable/1", "row"),
("/-/versions", "json_data"),
("/fixtures/-/query?sql=select+1", "database"),
),
Expand Down
16 changes: 0 additions & 16 deletions tests/test_table_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,22 +720,6 @@ async def test_view(ds_client):
]


@pytest.mark.xfail
@pytest.mark.asyncio
async def test_unit_filters(ds_client):
response = await ds_client.get(
"/fixtures/units.json?_shape=arrays&distance__lt=75km&frequency__gt=1kHz"
)
assert response.status_code == 200
data = response.json()

assert data["units"]["distance"] == "m"
assert data["units"]["frequency"] == "Hz"

assert len(data["rows"]) == 1
assert data["rows"][0][0] == 2


def test_page_size_matching_max_returned_rows(
app_client_returned_rows_matches_page_size,
):
Expand Down

0 comments on commit 39dfc7d

Please sign in to comment.