Coverage for src\datasette_reconcile\utils.py: 82%
84 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-12-20 00:57 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-12-20 00:57 +0000
1import sqlite3
2import warnings
4from datasette.utils import HASH_LENGTH
5from datasette.utils.asgi import Forbidden, NotFound
7from datasette_reconcile.settings import DEFAULT_TYPE, SQLITE_VERSION_WARNING
9PERMISSION_TUPLE_SIZE = 2
12class ReconcileError(Exception):
13 pass
16async def check_permissions(request, permissions, ds):
17 "permissions is a list of (action, resource) tuples or 'action' strings"
18 "from https://github.com/simonw/datasette/blob/main/datasette/views/base.py#L69"
19 for permission in permissions:
20 if isinstance(permission, str):
21 action = permission
22 resource = None
23 elif isinstance(permission, (tuple, list)) and len(permission) == PERMISSION_TUPLE_SIZE: 23 ↛ 26line 23 didn't jump to line 26, because the condition on line 23 was never false
24 action, resource = permission
25 else:
26 msg = f"permission should be string or tuple of two items: {permission!r}"
27 raise AssertionError(msg)
28 ok = await ds.permission_allowed(
29 request.actor,
30 action,
31 resource=resource,
32 default=None,
33 )
34 if ok is not None: 34 ↛ 35line 34 didn't jump to line 35, because the condition on line 34 was never true
35 if ok:
36 return
37 else:
38 raise Forbidden(action)
41async def check_config(config, db, table):
42 is_view = bool(await db.get_view_definition(table))
43 table_exists = bool(await db.table_exists(table))
44 if not is_view and not table_exists:
45 msg = f"Table not found: {table}"
46 raise NotFound(msg)
48 if not config:
49 msg = f"datasette-reconcile not configured for table {table} in database {db!s}"
50 raise NotFound(msg)
52 pks = await db.primary_keys(table)
53 if not pks: 53 ↛ 54line 53 didn't jump to line 54, because the condition on line 53 was never true
54 pks = ["rowid"]
56 if "id_field" not in config and len(pks) == 1:
57 config["id_field"] = pks[0]
58 elif "id_field" not in config: 58 ↛ 59line 58 didn't jump to line 59, because the condition on line 58 was never true
59 msg = "Could not determine an ID field to use"
60 raise ReconcileError(msg)
61 if "name_field" not in config:
62 msg = "Name field must be defined to activate reconciliation"
63 raise ReconcileError(msg)
64 if "type_field" not in config and "type_default" not in config:
65 config["type_default"] = [DEFAULT_TYPE]
67 if "max_limit" in config and not isinstance(config["max_limit"], int):
68 msg = "max_limit in reconciliation config must be an integer"
69 raise TypeError(msg)
70 if "type_default" in config:
71 if not isinstance(config["type_default"], list):
72 msg = "type_default should be a list of objects"
73 raise ReconcileError(msg)
74 for t in config["type_default"]:
75 if not isinstance(t, dict):
76 msg = "type_default values should be objects"
77 raise ReconcileError(msg)
78 if not isinstance(t.get("id"), str):
79 msg = "type_default 'id' values should be strings"
80 raise ReconcileError(msg)
81 if not isinstance(t.get("name"), str):
82 msg = "type_default 'name' values should be strings"
83 raise ReconcileError(msg)
85 if "view_url" in config:
86 if "{{id}}" not in config["view_url"]:
87 msg = "View URL must contain {{id}}"
88 raise ReconcileError(msg)
90 config["fts_table"] = await db.fts_table(table)
92 # let's show a warning if sqlite3 version is less than 3.30.0
93 # full text search results will fail for < 3.30.0 if the table
94 # name contains special characters
95 if config["fts_table"] and ( 95 ↛ 102line 95 didn't jump to line 102, because the condition on line 95 was never true
96 (
97 sqlite3.sqlite_version_info[0] == SQLITE_VERSION_WARNING[0]
98 and sqlite3.sqlite_version_info[1] < SQLITE_VERSION_WARNING[1]
99 )
100 or sqlite3.sqlite_version_info[0] < SQLITE_VERSION_WARNING[0]
101 ):
102 warnings.warn(
103 "Full Text Search queries for sqlite3 version < 3.30.0 wil fail if table name contains special characters",
104 stacklevel=2,
105 )
107 return config
110def get_select_fields(config):
111 select_fields = [config["id_field"], config["name_field"], *config.get("additional_fields", [])]
112 if config.get("type_field"):
113 select_fields.append(config["type_field"])
114 return select_fields
117def get_view_url(ds, database, table):
118 id_str = "{{id}}"
119 if hasattr(ds, "urls"): 119 ↛ 121line 119 didn't jump to line 121, because the condition on line 119 was never false
120 return ds.urls.row(database, table, id_str)
121 db = ds.databases[database]
122 base_url = ds.config("base_url")
123 if ds.config("hash_urls") and db.hash:
124 return f"{base_url}{database}-{db.hash[:HASH_LENGTH]}/{table}/{id_str}"
125 else:
126 return f"{base_url}{database}/{table}/{id_str}"