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/__about__.py +1 -1
- laketower/cli.py +43 -4
- laketower/config.py +10 -0
- laketower/static/datatables.bundle.js +27931 -0
- laketower/static/datatables.js +55 -0
- laketower/static/vendor/datatables.net-bs5/dataTables.bootstrap5.css +610 -0
- laketower/static/vendor/datatables.net-columncontrol-bs5/columnControl.bootstrap5.min.css +1 -0
- laketower/tables.py +30 -2
- laketower/templates/_base.html +14 -2
- laketower/templates/queries/view.html +24 -2
- laketower/templates/tables/query.html +20 -4
- laketower/templates/tables/view.html +38 -38
- laketower/web.py +38 -6
- {laketower-0.6.3.dist-info → laketower-0.6.5.dist-info}/METADATA +15 -1
- {laketower-0.6.3.dist-info → laketower-0.6.5.dist-info}/RECORD +18 -14
- {laketower-0.6.3.dist-info → laketower-0.6.5.dist-info}/WHEEL +0 -0
- {laketower-0.6.3.dist-info → laketower-0.6.5.dist-info}/entry_points.txt +0 -0
- {laketower-0.6.3.dist-info → laketower-0.6.5.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
|
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(
|
|
322
|
+
raise ValueError(f"Error: {e}") from e
|
|
295
323
|
|
|
296
324
|
|
|
297
325
|
def import_file_to_table(
|
laketower/templates/_base.html
CHANGED
|
@@ -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
|
-
<
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
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=
|
|
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=
|
|
5
|
-
laketower/config.py,sha256=
|
|
6
|
-
laketower/tables.py,sha256=
|
|
7
|
-
laketower/web.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
27
|
-
laketower-0.6.
|
|
28
|
-
laketower-0.6.
|
|
29
|
-
laketower-0.6.
|
|
30
|
-
laketower-0.6.
|
|
31
|
-
laketower-0.6.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|