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

1import sqlite3 

2import warnings 

3 

4from datasette.utils import HASH_LENGTH 

5from datasette.utils.asgi import Forbidden, NotFound 

6 

7from datasette_reconcile.settings import DEFAULT_TYPE, SQLITE_VERSION_WARNING 

8 

9PERMISSION_TUPLE_SIZE = 2 

10 

11 

12class ReconcileError(Exception): 

13 pass 

14 

15 

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) 

39 

40 

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) 

47 

48 if not config: 

49 msg = f"datasette-reconcile not configured for table {table} in database {db!s}" 

50 raise NotFound(msg) 

51 

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"] 

55 

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] 

66 

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) 

84 

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) 

89 

90 config["fts_table"] = await db.fts_table(table) 

91 

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 ) 

106 

107 return config 

108 

109 

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 

115 

116 

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}"