laketower 0.6.3__py3-none-any.whl → 0.6.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of laketower might be problematic. Click here for more details.

laketower/tables.py CHANGED
@@ -10,6 +10,7 @@ import pyarrow.dataset as padataset
10
10
  import pydantic
11
11
  import sqlglot
12
12
  import sqlglot.dialects.duckdb
13
+ import sqlglot.errors
13
14
  import sqlglot.expressions
14
15
 
15
16
  from laketower.config import ConfigTable, TableFormats
@@ -230,7 +231,11 @@ def load_datasets(table_configs: list[ConfigTable]) -> dict[str, padataset.Datas
230
231
 
231
232
 
232
233
  def extract_query_parameter_names(sql: str) -> set[str]:
233
- parsed_sql = sqlglot.parse(sql, dialect=sqlglot.dialects.duckdb.DuckDB)
234
+ try:
235
+ parsed_sql = sqlglot.parse(sql, dialect=sqlglot.dialects.duckdb.DuckDB)
236
+ except sqlglot.errors.SqlglotError as e:
237
+ raise ValueError(f"Error: {e}") from e
238
+
234
239
  return {
235
240
  str(node.this)
236
241
  for statement in parsed_sql
@@ -270,6 +275,29 @@ def generate_table_statistics_query(table_name: str) -> str:
270
275
  return query_expr.sql(dialect=sqlglot.dialects.duckdb.DuckDB, identify="always")
271
276
 
272
277
 
278
+ def limit_query(sql_query: str, max_limit: int) -> str:
279
+ try:
280
+ query_ast = sqlglot.parse(sql_query, dialect=sqlglot.dialects.duckdb.DuckDB)
281
+ except sqlglot.errors.SqlglotError as e:
282
+ raise ValueError(f"Error: {e}") from e
283
+
284
+ if query_ast and isinstance(query_ast[-1], sqlglot.expressions.Select):
285
+ limit_wrapper = (
286
+ sqlglot.select("*")
287
+ .from_(sqlglot.expressions.Subquery(this=query_ast[-1]))
288
+ .limit(max_limit)
289
+ )
290
+ query_ast[-1] = limit_wrapper
291
+
292
+ return "; ".join(
293
+ [
294
+ stmt.sql(dialect=sqlglot.dialects.duckdb.DuckDB, identify="always")
295
+ for stmt in query_ast
296
+ if stmt is not None
297
+ ]
298
+ )
299
+
300
+
273
301
  def execute_query(
274
302
  tables_datasets: dict[str, padataset.Dataset],
275
303
  sql_query: str,
@@ -291,7 +319,7 @@ def execute_query(
291
319
  conn.execute(f'create view "{table_name}" as select * from "{view_name}"') # nosec B608
292
320
  return conn.execute(sql_query, parameters=sql_params).fetch_arrow_table()
293
321
  except duckdb.Error as e:
294
- raise ValueError(str(e)) from e
322
+ raise ValueError(f"Error: {e}") from e
295
323
 
296
324
 
297
325
  def import_file_to_table(
@@ -8,6 +8,15 @@
8
8
  <link href="{{ url_for('static', path='/vendor/bootstrap-icons/bootstrap-icons.min.css') }}" rel="stylesheet">
9
9
  <link href="{{ url_for('static', path='/vendor/halfmoon/halfmoon.min.css') }}" rel="stylesheet">
10
10
  <link href="{{ url_for('static', path='/vendor/halfmoon/halfmoon.modern.css') }}" rel="stylesheet">
11
+ <link href="{{ url_for('static', path='/vendor/datatables.net-bs5/dataTables.bootstrap5.css') }}" rel="stylesheet">
12
+ <link href="{{ url_for('static', path='/vendor/datatables.net-columncontrol-bs5/columnControl.bootstrap5.min.css') }}" rel="stylesheet">
13
+ <style>
14
+ /* Only override overflow when DataTables is initialized */
15
+ .table-responsive:has(table.dataTable) {
16
+ overflow: visible !important;
17
+ }
18
+ </style>
19
+ {% block extra_styles %}{% endblock %}
11
20
  </head>
12
21
 
13
22
  <body class="ps-md-sbwidth">
@@ -28,6 +37,7 @@
28
37
  </div>
29
38
  <div class="offcanvas-body d-flex flex-column">
30
39
  <ul class="sidebar-nav flex-grow-1 overflow-auto">
40
+ {% if not request.app.state.config.settings.web.hide_tables %}
31
41
  <li>
32
42
  <h6 class="sidebar-header">Tables</h6>
33
43
  </li>
@@ -36,7 +46,7 @@
36
46
  {% set table_url = '/tables/' + table.name %}
37
47
  <li class="nav-item">
38
48
  <a
39
- class="text-truncate nav-link{% if request.url.path.startswith(table_url) %} active{% endif %}"
49
+ class="text-truncate nav-link{% if request.url.path.startswith(table_url + '/') or request.url.path == table_url %} active{% endif %}"
40
50
  href="{{ table_url }}"
41
51
  aria-current="page"
42
52
  >
@@ -47,6 +57,7 @@
47
57
  {% endfor %}
48
58
 
49
59
  <li><hr class="sidebar-divider"></li>
60
+ {% endif %}
50
61
 
51
62
  <li>
52
63
  <h6 class="sidebar-header">Queries</h6>
@@ -56,7 +67,7 @@
56
67
  {% set query_url = '/queries/' + query.name + '/view' %}
57
68
  <li class="nav-item">
58
69
  <a
59
- class="text-truncate nav-link{% if request.url.path.startswith(query_url) %} active{% endif %}"
70
+ class="text-truncate nav-link{% if request.url.path.startswith(query_url + '/') or request.url.path == query_url %} active{% endif %}"
60
71
  href="{{ query_url }}"
61
72
  aria-current="page"
62
73
  >
@@ -164,6 +175,7 @@
164
175
  </div>
165
176
 
166
177
  <script src="{{ url_for('static', path='/vendor/bootstrap/bootstrap.bundle.min.js') }}"></script>
178
+ <script src="{{ url_for('static', path='/datatables.bundle.js') }}"></script>
167
179
  {% block extra_scripts %}{% endblock %}
168
180
  </body>
169
181
 
@@ -1,5 +1,8 @@
1
1
  {% extends "_base.html" %}
2
2
 
3
+ {% block extra_styles %}
4
+ {% endblock %}
5
+
3
6
  {% block body %}
4
7
  <div class="row">
5
8
  <div class="col">
@@ -48,14 +51,21 @@
48
51
  {{ error.message }}
49
52
  </div>
50
53
  {% else %}
54
+ <h3>Results</h3>
51
55
  <div class="d-flex justify-content-between align-items-center mb-2">
52
- <h3>Results</h3>
56
+ <p>
57
+ <i class="{% if truncated_results %}bi-database-exclamation{% else %}bi-database-check{% endif %}" aria-hidden="true"></i>
58
+ {{ query_results.num_rows }} rows returned{% if truncated_results %} (truncated){% endif %}
59
+ <br>
60
+ <i class="bi-speedometer" aria-hidden="true"></i>
61
+ Query execution time: {{ execution_time_ms | round(2) }}ms
62
+ </p>
53
63
  <a href="/tables/query/csv?sql={{ query.sql | urlencode }}" class="btn btn-outline-secondary btn-sm">
54
64
  <i class="bi-download" aria-hidden="true"></i> Export CSV
55
65
  </a>
56
66
  </div>
57
67
  <div class="table-responsive">
58
- <table class="table table-sm table-bordered table-striped table-hover">
68
+ <table id="resultsTable" class="table table-sm table-bordered table-striped table-hover">
59
69
  <thead>
60
70
  <tr>
61
71
  {% for column in query_results.column_names %}
@@ -78,3 +88,15 @@
78
88
  </div>
79
89
  </div>
80
90
  {% endblock %}
91
+
92
+ {% block extra_scripts %}
93
+ <script>
94
+ window.addEventListener("DOMContentLoaded", () => {
95
+ {% if not error %}
96
+ const arrowTypes = {{ query_results.schema.types | map('string') | list | tojson }}
97
+ const columnTypes = datatables.arrowTypesToDataTables(arrowTypes)
98
+ const dataTable = datatables.createDataTable('#resultsTable', {columnTypes})
99
+ {% endif %}
100
+ })
101
+ </script>
102
+ {% endblock %}
@@ -1,6 +1,9 @@
1
1
  {% extends "_base.html" %}
2
2
  {% import 'tables/_macros.html' as table_macros %}
3
3
 
4
+ {% block extra_styles %}
5
+ {% endblock %}
6
+
4
7
  {% block body %}
5
8
  <div class="row">
6
9
  <div class="col">
@@ -37,14 +40,21 @@
37
40
  {{ error.message }}
38
41
  </div>
39
42
  {% else %}
43
+ <h3>Results</h3>
40
44
  <div class="d-flex justify-content-between align-items-center mb-2">
41
- <h3>Results</h3>
45
+ <p>
46
+ <i class="{% if truncated_results %}bi-database-exclamation{% else %}bi-database-check{% endif %}" aria-hidden="true"></i>
47
+ {{ table_results.num_rows }} rows returned{% if truncated_results %} (truncated){% endif %}
48
+ <br>
49
+ <i class="bi-speedometer" aria-hidden="true"></i>
50
+ Query execution time: {{ execution_time_ms | round(2) }}ms
51
+ </p>
42
52
  <a href="/tables/query/csv?sql={{ sql_query | urlencode }}" class="btn btn-outline-secondary btn-sm">
43
53
  <i class="bi-download" aria-hidden="true"></i> Export CSV
44
54
  </a>
45
55
  </div>
46
56
  <div class="table-responsive">
47
- <table class="table table-sm table-bordered table-striped table-hover">
57
+ <table id="resultsTable" class="table table-sm table-bordered table-striped table-hover">
48
58
  <thead>
49
59
  <tr>
50
60
  {% for column in table_results.column_names %}
@@ -52,7 +62,7 @@
52
62
  {% endfor %}
53
63
  </tr>
54
64
  </thead>
55
- <tbody class="table-group-divider">
65
+ <tbody>
56
66
  {% for row in table_results.to_pylist() %}
57
67
  <tr>
58
68
  {% for column in table_results.column_names %}
@@ -73,9 +83,15 @@
73
83
  <script>
74
84
  window.addEventListener("DOMContentLoaded", () => {
75
85
  const textArea = document.querySelector("textarea#sql-editor")
76
- textArea.style.display = "none";
86
+ textArea.style.display = "none"
77
87
  const sqlSchema = {{ sql_schema | tojson }}
78
88
  const sqlEditor = editor.createEditor(textArea, { readOnly: false, dialect: 'duckdb', schema: sqlSchema})
89
+
90
+ {% if not error %}
91
+ const arrowTypes = {{ table_results.schema.types | map('string') | list | tojson }}
92
+ const columnTypes = datatables.arrowTypesToDataTables(arrowTypes)
93
+ const dataTable = datatables.createDataTable('#resultsTable', {columnTypes})
94
+ {% endif %}
79
95
  })
80
96
  </script>
81
97
  {% endblock %}
@@ -31,7 +31,7 @@
31
31
  <i class="bi-arrow-down-up" aria-hidden="true"></i>
32
32
  </a>
33
33
  {% endif %}
34
- {% set other_cols = table_results.columns | list | reject('equalto', column) | list %}
34
+ {% set other_cols = table_results.column_names | list | reject('equalto', column) | list %}
35
35
  {% set cols_args = [] %}
36
36
  {% for col in other_cols %}
37
37
  {% set tmp = cols_args.append(('cols', col)) %}
@@ -53,48 +53,48 @@
53
53
  {% endfor %}
54
54
  </tbody>
55
55
  </table>
56
+ </div>
56
57
 
57
- <div class="d-flex justify-content-between">
58
- <div class="row">
59
- <form class="col" action="{{ request.url.path }}" method="get">
60
- {% for param_name, param_val in request.query_params.multi_items() %}
61
- {% if param_name != 'limit' %}
62
- <input type="hidden" name="{{ param_name }}" value="{{ param_val }}">
63
- {% endif %}
64
- {% endfor %}
58
+ <div class="d-flex justify-content-between">
59
+ <div class="row">
60
+ <form class="col" action="{{ request.url.path }}" method="get">
61
+ {% for param_name, param_val in request.query_params.multi_items() %}
62
+ {% if param_name != 'limit' %}
63
+ <input type="hidden" name="{{ param_name }}" value="{{ param_val }}">
64
+ {% endif %}
65
+ {% endfor %}
65
66
 
66
- <div class="input-group">
67
- <input id="limit-input" name="limit" type="number" class="form-control" min="1" max="10000" value="{{ request.query_params.limit or default_limit }}">
68
- <button type="submit" class="btn btn-primary">Limit</button>
69
- </div>
70
- </form>
67
+ <div class="input-group">
68
+ <input id="limit-input" name="limit" type="number" class="form-control" min="1" max="10000" value="{{ request.query_params.limit or default_limit }}">
69
+ <button type="submit" class="btn btn-primary">Limit</button>
70
+ </div>
71
+ </form>
71
72
 
72
- <form class="col" ="{{ request.url.path }}" method="get">
73
- {% for param_name, param_val in request.query_params.multi_items() %}
74
- {% if param_name != 'version' %}
75
- <input type="hidden" name="{{ param_name }}" value="{{ param_val }}">
76
- {% endif %}
77
- {% endfor %}
78
-
79
- <div class="input-group">
80
- <input
81
- id="version-input"
82
- name="version"
83
- type="number"
84
- class="form-control"
85
- min="0"
86
- max="{{ table_metadata.version }}"
87
- value="{{ request.query_params.version or table_metadata.version }}"
88
- >
89
- <button type="submit" class="btn btn-primary">Version</button>
90
- </div>
91
- </form>
92
- </div>
73
+ <form class="col" ="{{ request.url.path }}" method="get">
74
+ {% for param_name, param_val in request.query_params.multi_items() %}
75
+ {% if param_name != 'version' %}
76
+ <input type="hidden" name="{{ param_name }}" value="{{ param_val }}">
77
+ {% endif %}
78
+ {% endfor %}
93
79
 
94
- <a href="/tables/query?sql={{ sql_query | urlencode }}" class="btn btn-primary" role="button">
95
- <i class="bi-code" aria-hidden="true"></i> SQL Query
96
- </a>
80
+ <div class="input-group">
81
+ <input
82
+ id="version-input"
83
+ name="version"
84
+ type="number"
85
+ class="form-control"
86
+ min="0"
87
+ max="{{ table_metadata.version }}"
88
+ value="{{ request.query_params.version or table_metadata.version }}"
89
+ >
90
+ <button type="submit" class="btn btn-primary">Version</button>
91
+ </div>
92
+ </form>
97
93
  </div>
94
+
95
+ <a href="/tables/query?sql={{ sql_query | urlencode }}" class="btn btn-primary" role="button">
96
+ <i class="bi-code" aria-hidden="true"></i> SQL Query
97
+ </a>
98
98
  </div>
99
99
  </div>
100
100
  </div>
laketower/web.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import io
2
+ import time
2
3
  import urllib.parse
3
4
  from dataclasses import dataclass
4
5
  from pathlib import Path
@@ -24,6 +25,7 @@ from laketower.tables import (
24
25
  generate_table_statistics_query,
25
26
  generate_table_query,
26
27
  import_file_to_table,
28
+ limit_query,
27
29
  load_datasets,
28
30
  load_table,
29
31
  )
@@ -87,17 +89,32 @@ def get_tables_query(request: Request, sql: str) -> HTMLResponse:
87
89
  table_name: dataset.schema.names
88
90
  for table_name, dataset in tables_dataset.items()
89
91
  }
90
- sql_param_names = extract_query_parameter_names(sql)
91
- sql_params = {
92
- name: request.query_params.get(name) or "" for name in sql_param_names
93
- }
94
92
 
95
93
  try:
96
- results = execute_query(tables_dataset, sql, sql_params=sql_params)
94
+ sql_param_names = extract_query_parameter_names(sql)
95
+ sql_params = {
96
+ name: request.query_params.get(name) or "" for name in sql_param_names
97
+ }
98
+ except ValueError:
99
+ sql_params = {}
100
+
101
+ try:
102
+ sql_query = limit_query(sql, config.settings.max_query_rows + 1)
103
+
104
+ start_time = time.perf_counter()
105
+ results = execute_query(tables_dataset, sql_query, sql_params=sql_params)
106
+ execution_time_ms = (time.perf_counter() - start_time) * 1000
107
+
108
+ truncated = results.num_rows > config.settings.max_query_rows
109
+ results = results.slice(
110
+ 0, min(results.num_rows, config.settings.max_query_rows)
111
+ )
97
112
  error = None
98
113
  except ValueError as e:
99
114
  error = {"message": str(e)}
100
115
  results = None
116
+ truncated = False
117
+ execution_time_ms = None
101
118
 
102
119
  return templates.TemplateResponse(
103
120
  request=request,
@@ -107,6 +124,8 @@ def get_tables_query(request: Request, sql: str) -> HTMLResponse:
107
124
  "tables": config.tables,
108
125
  "queries": config.queries,
109
126
  "table_results": results,
127
+ "truncated_results": truncated,
128
+ "execution_time_ms": execution_time_ms,
110
129
  "sql_query": sql,
111
130
  "sql_schema": sql_schema,
112
131
  "sql_params": sql_params,
@@ -387,11 +406,22 @@ def get_query_view(request: Request, query_id: str) -> Response:
387
406
  }
388
407
 
389
408
  try:
390
- results = execute_query(tables_dataset, query_config.sql, sql_params=sql_params)
409
+ sql_query = limit_query(query_config.sql, config.settings.max_query_rows + 1)
410
+
411
+ start_time = time.perf_counter()
412
+ results = execute_query(tables_dataset, sql_query, sql_params=sql_params)
413
+ execution_time_ms = (time.perf_counter() - start_time) * 1000
414
+
415
+ truncated = results.num_rows > config.settings.max_query_rows
416
+ results = results.slice(
417
+ 0, min(results.num_rows, config.settings.max_query_rows)
418
+ )
391
419
  error = None
392
420
  except ValueError as e:
393
421
  error = {"message": str(e)}
394
422
  results = None
423
+ truncated = False
424
+ execution_time_ms = None
395
425
 
396
426
  return templates.TemplateResponse(
397
427
  request=request,
@@ -402,6 +432,8 @@ def get_query_view(request: Request, query_id: str) -> Response:
402
432
  "queries": config.queries,
403
433
  "query": query_config,
404
434
  "query_results": results,
435
+ "truncated_results": truncated,
436
+ "execution_time_ms": execution_time_ms,
405
437
  "sql_params": sql_params,
406
438
  "error": error,
407
439
  },
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: laketower
3
- Version: 0.6.3
3
+ Version: 0.6.5
4
4
  Summary: Oversee your lakehouse
5
5
  Project-URL: Repository, https://github.com/datalpia/laketower
6
6
  Project-URL: Issues, https://github.com/datalpia/laketower/issues
@@ -35,6 +35,7 @@ Requires-Dist: python-multipart
35
35
  Requires-Dist: pyyaml
36
36
  Requires-Dist: rich
37
37
  Requires-Dist: sqlglot
38
+ Requires-Dist: tzdata
38
39
  Requires-Dist: uvicorn
39
40
  Description-Content-Type: text/markdown
40
41
 
@@ -91,6 +92,11 @@ Laketower configuration is based on a static YAML configuration file allowing to
91
92
  Format:
92
93
 
93
94
  ```yaml
95
+ settings:
96
+ max_query_rows: 1000
97
+ web:
98
+ hide_tables: false
99
+
94
100
  tables:
95
101
  - name: <table_name>
96
102
  uri: <local path to table>
@@ -530,6 +536,8 @@ $ laketower -c demo/laketower.yml tables query "select date_trunc('day', time) a
530
536
  │ 2025-02-11 00:00:00+01:00 │ 4.833333373069763 │
531
537
  │ 2025-02-10 00:00:00+01:00 │ 2.1083333243926368 │
532
538
  └───────────────────────────┴────────────────────┘
539
+ 3 rows returned
540
+ Execution time: 33.72ms
533
541
  ```
534
542
 
535
543
  Use named parameters within a giving query (note: escape `$` prefixes properly!):
@@ -544,6 +552,8 @@ $ laketower -c demo/laketower.yml tables query "select date_trunc('day', time) a
544
552
  │ 2025-01-30 00:00:00+01:00 │ 8.900000015894571 │
545
553
  │ 2025-01-29 00:00:00+01:00 │ 7.770833313465118 │
546
554
  └───────────────────────────┴────────────────────┘
555
+ 4 rows returned
556
+ Execution time: 30.59ms
547
557
  ```
548
558
 
549
559
  Export query results to CSV:
@@ -591,6 +601,8 @@ $ laketower -c demo/laketower.yml queries view daily_avg_temperature
591
601
  │ 2025-02-11 00:00:00+01:00 │ 5.0 │
592
602
  │ 2025-02-12 00:00:00+01:00 │ 5.0 │
593
603
  └───────────────────────────┴─────────────────┘
604
+ 18 rows returned
605
+ Execution time: 39.52ms
594
606
  ```
595
607
 
596
608
  Executing a predefined query with parameters (here `start_date` and `end_date`):
@@ -607,6 +619,8 @@ $ laketower -c demo/laketower.yml queries view daily_avg_temperature_params -p s
607
619
  │ 2025-02-04 00:00:00+01:00 │ 3.0 │
608
620
  │ 2025-02-05 00:00:00+01:00 │ 3.0 │
609
621
  └───────────────────────────┴─────────────────┘
622
+ 6 rows returned
623
+ Execution time: 32.08ms
610
624
  ```
611
625
 
612
626
  ## License
@@ -1,31 +1,35 @@
1
- laketower/__about__.py,sha256=zYiFHqR7JwbvdK9dvKrh-RTNfUqjHUwC4CTcFAPVYLc,22
1
+ laketower/__about__.py,sha256=KDgkBrBsBSUzbLgrOZ89YsNN06fU4j5bmcuEwo6q5pg,22
2
2
  laketower/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  laketower/__main__.py,sha256=czKxJKG8OfncnxWmpaOWx7b1JBwFnZNQi7wKSTncB4M,108
4
- laketower/cli.py,sha256=2oISOgHy1CpJLVwReVNUQSCHcrgV-dPSPmGdPKwqc1o,16624
5
- laketower/config.py,sha256=flRp9yb4DK4xQUJu5g6Mr_gSMtTyJ4_jBAb-08ROvqQ,3453
6
- laketower/tables.py,sha256=AiWCMBCY3TnX1aeLwzE0rEUdTALpMHrDXy9Bz5fjv7c,10857
7
- laketower/web.py,sha256=e_qGbMR80zZaAY1YwEpqe2bmNEDZw3YQf7OEWjrcIkM,13536
4
+ laketower/cli.py,sha256=6jwvG2ZjNQNOJJS6fWzOcr8cp2u0VMgMuBd9vIIaS8o,18071
5
+ laketower/config.py,sha256=9zY69hIDSYcNqZtWGu-Jv23BFmLtHh2p03DZq9-1s_c,3703
6
+ laketower/tables.py,sha256=o5OxrSTamUFZC8j7qBnJWEAZ_9tG1sPzif2XFAJjIM0,11713
7
+ laketower/web.py,sha256=mm-zNsi8mc_5ig8TWXt4p8k2GghFvVIIjpAguAGT6Nw,14696
8
8
  laketower/static/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ laketower/static/datatables.bundle.js,sha256=k9QXhh0gBLtw5gJRJToDrgdNVPBWAKRdNfBmYvziuiA,814319
10
+ laketower/static/datatables.js,sha256=lsBkx3Kt9GwGDZ97JEgDkwvfBlzK3THlN1K43iOrqvM,1857
9
11
  laketower/static/editor.bundle.js,sha256=Wa1bS0xWwKDjHKPTdXd-PX41JAGkCjd0VLBCF-SyXWk,1159343
10
12
  laketower/static/editor.js,sha256=C8saQJH68X7CtdRyBmvrQxsDJ013YWoMfWF-6hzf_5s,15870
11
13
  laketower/static/vendor/bootstrap/bootstrap.bundle.min.js,sha256=5P1JGBOIxI7FBAvT_mb1fCnI5n_NhQKzNUuW7Hq0fMc,80496
12
14
  laketower/static/vendor/bootstrap-icons/bootstrap-icons.min.css,sha256=pdY4ejLKO67E0CM2tbPtq1DJ3VGDVVdqAR6j3ZwdiE4,87008
13
15
  laketower/static/vendor/bootstrap-icons/fonts/bootstrap-icons.woff,sha256=9VUTt7WRy4SjuH_w406iTUgx1v7cIuVLkRymS1tUShU,180288
14
16
  laketower/static/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2,sha256=bHVxA2ShylYEJncW9tKJl7JjGf2weM8R4LQqtm_y6mE,134044
17
+ laketower/static/vendor/datatables.net-bs5/dataTables.bootstrap5.css,sha256=OPNPBvNyQahoUZ5gvqCCz9nPPIoUug2nn2_fpg9D8H0,21837
18
+ laketower/static/vendor/datatables.net-columncontrol-bs5/columnControl.bootstrap5.min.css,sha256=JwBYwZbQtbi-PwceKQ4KeqQp6E0QnGzeUSbaX-26UZc,13574
15
19
  laketower/static/vendor/halfmoon/halfmoon.min.css,sha256=RjeFzczeuZHCyS-Gvz-kleETzBF_o84ZRHukze_yv6o,369168
16
20
  laketower/static/vendor/halfmoon/halfmoon.modern.css,sha256=DD6elX-jPmbFYPsGvzodUv2-9FHkxHlVtQi0_RJVULs,10576
17
- laketower/templates/_base.html,sha256=JB3n9ziRxFl1lv8TToXgm2EyihvIuaE4rZi4n9XwXv0,6342
21
+ laketower/templates/_base.html,sha256=bRSdILczy9V05fMNu-iz30dLB6e14XFUhurBu2AlGK4,7065
18
22
  laketower/templates/index.html,sha256=dLF2Og0qgzBkvGyVRidRNzTv0u4o97ifOx1jVeig8Kg,59
19
- laketower/templates/queries/view.html,sha256=jrBItsibdcpo18qwwGVsasY41tSWAvz6ndaMwEkNqEg,2582
23
+ laketower/templates/queries/view.html,sha256=2vEqpOsgx6lPCYEcxW5VM60oERwayjrDlSaCznJ6MkY,3426
20
24
  laketower/templates/tables/_macros.html,sha256=sCI1TOFW0QA74oSXW87H6dNTudOs7n-FretnTPFcRh4,1174
21
25
  laketower/templates/tables/history.html,sha256=a5GBLXCiLlbWno5eR0XT5i_oMAghylUBBFOpr27NB3Q,1853
22
26
  laketower/templates/tables/import.html,sha256=bQZwRrv84tDBuf0AHJyc7L-PjW-XSoZhMHNDIo6TP4c,2604
23
27
  laketower/templates/tables/index.html,sha256=saNdQbJAjMJAzayTk4rA5Mmw_bCXvor2WpghVmoWSAI,2507
24
- laketower/templates/tables/query.html,sha256=rtQtA011ScYO8WGnq_iRLgf3xUFVsSCCLpXzZJvP3ps,2633
28
+ laketower/templates/tables/query.html,sha256=lI_jHcXV-lxMIDyWhNcfFwwJUFLHIICue9usZ5obnx0,3344
25
29
  laketower/templates/tables/statistics.html,sha256=yVsu-hNPSQ3-IX-4Z4bukLR4nERvy5PwWENfkW24xyg,1858
26
- laketower/templates/tables/view.html,sha256=__Z-fDBHnud9iQcbYPZPGPQol2--a7TF4kT6K3001AE,4016
27
- laketower-0.6.3.dist-info/METADATA,sha256=4AGC-yZWzQQqQOyGP7PY2ijQotAg6XSn1ApFUSEiljE,27658
28
- laketower-0.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
- laketower-0.6.3.dist-info/entry_points.txt,sha256=sJpQgRwdeZhRBudNqBTqtHPCE-uLC9YgFXJY2CTEyCk,53
30
- laketower-0.6.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
31
- laketower-0.6.3.dist-info/RECORD,,
30
+ laketower/templates/tables/view.html,sha256=D5jkzzMM6YaFKPquTGnh--7tlNBtF0iztBN0ULvLePs,3947
31
+ laketower-0.6.5.dist-info/METADATA,sha256=HEAXi9UmhTPoKgaVSUm2TrJFsK31dlHwzlgSG7gEoVs,27905
32
+ laketower-0.6.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ laketower-0.6.5.dist-info/entry_points.txt,sha256=sJpQgRwdeZhRBudNqBTqtHPCE-uLC9YgFXJY2CTEyCk,53
34
+ laketower-0.6.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
35
+ laketower-0.6.5.dist-info/RECORD,,