diff --git a/datasette/app.py b/datasette/app.py index 1c730a73..d7d20016 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -37,7 +37,6 @@ from jinja2.exceptions import TemplateNotFound 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 ( diff --git a/datasette/filters.py b/datasette/filters.py index 585d4865..67d4170b 100644 --- a/datasette/filters.py +++ b/datasette/filters.py @@ -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""" @@ -413,20 +409,6 @@ class Filters: 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 = {} @@ -434,9 +416,7 @@ class Filters: 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): diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py index 073d6e86..7d248ee5 100644 --- a/datasette/utils/__init__.py +++ b/datasette/utils/__init__.py @@ -1368,7 +1368,6 @@ _table_config_keys = ( "fts_table", "fts_pk", "searchmode", - "units", ) diff --git a/datasette/views/base.py b/datasette/views/base.py index 2e78b0a5..aee06b01 100644 --- a/datasette/views/base.py +++ b/datasette/views/base.py @@ -8,8 +8,6 @@ import urllib from markupsafe import escape -import pint - from datasette.database import QueryInterrupted from datasette.utils.asgi import Request from datasette.utils import ( @@ -32,8 +30,6 @@ from datasette.utils.asgi import ( BadRequest, ) -ureg = pint.UnitRegistry() - class DatasetteError(Exception): def __init__( diff --git a/datasette/views/row.py b/datasette/views/row.py index 6180446f..d802994e 100644 --- a/datasette/views/row.py +++ b/datasette/views/row.py @@ -103,7 +103,6 @@ class RowView(DataView): "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(","): diff --git a/datasette/views/table.py b/datasette/views/table.py index ba0dd4f3..d71efeb0 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -43,7 +43,7 @@ from datasette.utils import ( 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 = ( @@ -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: @@ -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 @@ -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 diff --git a/docs/metadata.rst b/docs/metadata.rst index f3ca68ac..a3fa4040 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -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 `__. -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 diff --git a/setup.py b/setup.py index 22ec7963..47d796a3 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,6 @@ setup( "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", diff --git a/tests/fixtures.py b/tests/fixtures.py index af6b610b..0539b7c8 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -379,7 +379,6 @@ METADATA = { ], }, "no_primary_key": {"sortable_columns": [], "hidden": True}, - "units": {"units": {"distance": "m", "frequency": "Hz"}}, "primary_key_multiple_columns_explicit_label": { "label_column": "content2" }, @@ -507,16 +506,6 @@ CREATE TABLE "custom_foreign_key_label" ( 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 ); diff --git a/tests/plugins/my_plugin.py b/tests/plugins/my_plugin.py index 4ca4f989..e87353ea 100644 --- a/tests/plugins/my_plugin.py +++ b/tests/plugins/my_plugin.py @@ -5,18 +5,21 @@ from datasette import tracer 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) diff --git a/tests/test_api.py b/tests/test_api.py index 8a3fcc92..91f07563 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -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"], @@ -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" }, @@ -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": "***", diff --git a/tests/test_internals_database.py b/tests/test_internals_database.py index bc3c8fcf..0020668a 100644 --- a/tests/test_internals_database.py +++ b/tests/test_internals_database.py @@ -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", diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 5fad03ad..aa8f1578 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -424,8 +424,8 @@ def view_names_client(tmp_path_factory): ( ("/", "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"), ), diff --git a/tests/test_table_api.py b/tests/test_table_api.py index 11542cb0..615b36eb 100644 --- a/tests/test_table_api.py +++ b/tests/test_table_api.py @@ -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, ):