squirrels 0.5.0rc0__py3-none-any.whl → 0.5.1__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 squirrels might be problematic. Click here for more details.
- dateutils/__init__.py +6 -0
- dateutils/_enums.py +25 -0
- squirrels/dateutils.py → dateutils/_implementation.py +58 -111
- dateutils/types.py +6 -0
- squirrels/__init__.py +10 -12
- squirrels/_api_routes/__init__.py +5 -0
- squirrels/_api_routes/auth.py +271 -0
- squirrels/_api_routes/base.py +171 -0
- squirrels/_api_routes/dashboards.py +158 -0
- squirrels/_api_routes/data_management.py +148 -0
- squirrels/_api_routes/datasets.py +265 -0
- squirrels/_api_routes/oauth2.py +298 -0
- squirrels/_api_routes/project.py +252 -0
- squirrels/_api_server.py +245 -781
- squirrels/_arguments/__init__.py +0 -0
- squirrels/{arguments → _arguments}/init_time_args.py +7 -2
- squirrels/{arguments → _arguments}/run_time_args.py +13 -35
- squirrels/_auth.py +720 -212
- squirrels/_command_line.py +81 -41
- squirrels/_compile_prompts.py +147 -0
- squirrels/_connection_set.py +16 -7
- squirrels/_constants.py +29 -9
- squirrels/{_dashboards_io.py → _dashboards.py} +87 -6
- squirrels/_data_sources.py +570 -0
- squirrels/{dataset_result.py → _dataset_types.py} +2 -4
- squirrels/_exceptions.py +9 -37
- squirrels/_initializer.py +83 -59
- squirrels/_logging.py +117 -0
- squirrels/_manifest.py +129 -62
- squirrels/_model_builder.py +10 -52
- squirrels/_model_configs.py +3 -3
- squirrels/_model_queries.py +1 -1
- squirrels/_models.py +249 -118
- squirrels/{package_data → _package_data}/base_project/.env +16 -4
- squirrels/{package_data → _package_data}/base_project/.env.example +15 -3
- squirrels/{package_data → _package_data}/base_project/connections.yml +4 -3
- squirrels/{package_data → _package_data}/base_project/dashboards/dashboard_example.py +4 -4
- squirrels/_package_data/base_project/dashboards/dashboard_example.yml +22 -0
- squirrels/{package_data → _package_data}/base_project/duckdb_init.sql +1 -0
- squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
- squirrels/{package_data → _package_data}/base_project/models/builds/build_example.py +2 -2
- squirrels/{package_data → _package_data}/base_project/models/builds/build_example.sql +1 -1
- squirrels/{package_data → _package_data}/base_project/models/builds/build_example.yml +2 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +17 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +32 -0
- squirrels/_package_data/base_project/models/federates/federate_example.py +48 -0
- squirrels/_package_data/base_project/models/federates/federate_example.sql +21 -0
- squirrels/{package_data → _package_data}/base_project/models/federates/federate_example.yml +7 -7
- squirrels/{package_data → _package_data}/base_project/models/sources.yml +5 -6
- squirrels/{package_data → _package_data}/base_project/parameters.yml +32 -45
- squirrels/_package_data/base_project/pyconfigs/connections.py +18 -0
- squirrels/{package_data → _package_data}/base_project/pyconfigs/context.py +31 -22
- squirrels/_package_data/base_project/pyconfigs/parameters.py +141 -0
- squirrels/_package_data/base_project/pyconfigs/user.py +44 -0
- squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.yml +1 -1
- squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.yml +1 -1
- squirrels/_package_data/base_project/squirrels.yml.j2 +61 -0
- squirrels/_package_data/templates/dataset_results.html +112 -0
- squirrels/_package_data/templates/oauth_login.html +271 -0
- squirrels/_package_data/templates/squirrels_studio.html +20 -0
- squirrels/_parameter_configs.py +76 -55
- squirrels/_parameter_options.py +348 -0
- squirrels/_parameter_sets.py +53 -45
- squirrels/_parameters.py +1664 -0
- squirrels/_project.py +403 -242
- squirrels/_py_module.py +3 -2
- squirrels/_request_context.py +33 -0
- squirrels/_schemas/__init__.py +0 -0
- squirrels/_schemas/auth_models.py +167 -0
- squirrels/_schemas/query_param_models.py +75 -0
- squirrels/{_api_response_models.py → _schemas/response_models.py} +48 -18
- squirrels/_seeds.py +1 -1
- squirrels/_sources.py +23 -19
- squirrels/_utils.py +121 -39
- squirrels/_version.py +1 -1
- squirrels/arguments.py +7 -0
- squirrels/auth.py +4 -0
- squirrels/connections.py +3 -0
- squirrels/dashboards.py +2 -81
- squirrels/data_sources.py +14 -563
- squirrels/parameter_options.py +13 -348
- squirrels/parameters.py +14 -1266
- squirrels/types.py +16 -0
- {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/METADATA +42 -30
- squirrels-0.5.1.dist-info/RECORD +98 -0
- squirrels/package_data/base_project/dashboards/dashboard_example.yml +0 -22
- squirrels/package_data/base_project/macros/macros_example.sql +0 -15
- squirrels/package_data/base_project/models/dbviews/dbview_example.sql +0 -12
- squirrels/package_data/base_project/models/dbviews/dbview_example.yml +0 -26
- squirrels/package_data/base_project/models/federates/federate_example.py +0 -44
- squirrels/package_data/base_project/models/federates/federate_example.sql +0 -17
- squirrels/package_data/base_project/pyconfigs/connections.py +0 -14
- squirrels/package_data/base_project/pyconfigs/parameters.py +0 -93
- squirrels/package_data/base_project/pyconfigs/user.py +0 -23
- squirrels/package_data/base_project/squirrels.yml.j2 +0 -71
- squirrels-0.5.0rc0.dist-info/RECORD +0 -70
- /squirrels/{package_data → _package_data}/base_project/assets/expenses.db +0 -0
- /squirrels/{package_data → _package_data}/base_project/assets/weather.db +0 -0
- /squirrels/{package_data → _package_data}/base_project/docker/.dockerignore +0 -0
- /squirrels/{package_data → _package_data}/base_project/docker/Dockerfile +0 -0
- /squirrels/{package_data → _package_data}/base_project/docker/compose.yml +0 -0
- /squirrels/{package_data/base_project/.gitignore → _package_data/base_project/gitignore} +0 -0
- /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.csv +0 -0
- /squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.csv +0 -0
- /squirrels/{package_data → _package_data}/base_project/tmp/.gitignore +0 -0
- {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/WHEEL +0 -0
- {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/entry_points.txt +0 -0
- {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/licenses/LICENSE +0 -0
squirrels/_utils.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
from typing import Sequence, Optional, Union, TypeVar, Callable,
|
|
1
|
+
from typing import Sequence, Optional, Union, TypeVar, Callable, Iterable, Literal, Any
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from functools import lru_cache
|
|
5
|
-
from pydantic import BaseModel
|
|
6
4
|
import os, time, logging, json, duckdb, polars as pl, yaml
|
|
7
5
|
import jinja2 as j2, jinja2.nodes as j2_nodes
|
|
8
|
-
import sqlglot, sqlglot.expressions, asyncio
|
|
6
|
+
import sqlglot, sqlglot.expressions, asyncio, hashlib, inspect, base64
|
|
9
7
|
|
|
10
8
|
from . import _constants as c
|
|
11
9
|
from ._exceptions import ConfigurationError
|
|
@@ -20,7 +18,7 @@ polars_dtypes_to_sqrl_dtypes: dict[type[pl.DataType], list[str]] = {
|
|
|
20
18
|
pl.Int32: ["integer", "int", "int4"],
|
|
21
19
|
pl.Int64: ["bigint", "long", "int8"],
|
|
22
20
|
pl.Float32: ["float", "float4", "real"],
|
|
23
|
-
pl.Float64: ["double", "float8"],
|
|
21
|
+
pl.Float64: ["double", "float8", "decimal"], # Note: Polars Decimal type is considered unstable, so we use Float64 for "decimal"
|
|
24
22
|
pl.Boolean: ["boolean", "bool", "logical"],
|
|
25
23
|
pl.Date: ["date"],
|
|
26
24
|
pl.Time: ["time"],
|
|
@@ -35,12 +33,20 @@ sqrl_dtypes_to_polars_dtypes: dict[str, type[pl.DataType]] = {sqrl_type: k for k
|
|
|
35
33
|
## Other utility classes
|
|
36
34
|
|
|
37
35
|
class Logger(logging.Logger):
|
|
38
|
-
def
|
|
36
|
+
def info(self, msg: str, *, data: dict[str, Any] = {}, **kwargs) -> None:
|
|
37
|
+
super().info(msg, extra={"data": data}, **kwargs)
|
|
38
|
+
|
|
39
|
+
def log_activity_time(self, activity: str, start_timestamp: float, *, additional_data: dict[str, Any] = {}) -> None:
|
|
39
40
|
end_timestamp = time.time()
|
|
40
41
|
time_taken = round((end_timestamp-start_timestamp) * 10**3, 3)
|
|
41
|
-
data = {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
data = {
|
|
43
|
+
"activity": activity,
|
|
44
|
+
"start_timestamp": start_timestamp,
|
|
45
|
+
"end_timestamp": end_timestamp,
|
|
46
|
+
"time_taken_ms": time_taken,
|
|
47
|
+
**additional_data
|
|
48
|
+
}
|
|
49
|
+
self.info(f'Time taken for "{activity}": {time_taken}ms', data=data)
|
|
44
50
|
|
|
45
51
|
|
|
46
52
|
class EnvironmentWithMacros(j2.Environment):
|
|
@@ -85,14 +91,6 @@ class EnvironmentWithMacros(j2.Environment):
|
|
|
85
91
|
|
|
86
92
|
## Utility functions/variables
|
|
87
93
|
|
|
88
|
-
def log_activity_time(logger: logging.Logger, activity: str, start_timestamp: float, *, request_id: str | None = None) -> None:
|
|
89
|
-
end_timestamp = time.time()
|
|
90
|
-
time_taken = round((end_timestamp-start_timestamp) * 10**3, 3)
|
|
91
|
-
data = { "activity": activity, "start_timestamp": start_timestamp, "end_timestamp": end_timestamp, "time_taken_ms": time_taken }
|
|
92
|
-
info = { "request_id": request_id } if request_id else {}
|
|
93
|
-
logger.debug(f'Time taken for "{activity}": {time_taken}ms', extra={"data": data, "info": info})
|
|
94
|
-
|
|
95
|
-
|
|
96
94
|
def render_string(raw_str: str, *, base_path: str = ".", **kwargs) -> str:
|
|
97
95
|
"""
|
|
98
96
|
Given a template string, render it with the given keyword arguments
|
|
@@ -128,7 +126,7 @@ def read_file(filepath: FilePath) -> str:
|
|
|
128
126
|
|
|
129
127
|
def normalize_name(name: str) -> str:
|
|
130
128
|
"""
|
|
131
|
-
Normalizes names to the convention of the squirrels manifest file.
|
|
129
|
+
Normalizes names to the convention of the squirrels manifest file (with underscores instead of dashes).
|
|
132
130
|
|
|
133
131
|
Arguments:
|
|
134
132
|
name: The name to normalize.
|
|
@@ -141,7 +139,7 @@ def normalize_name(name: str) -> str:
|
|
|
141
139
|
|
|
142
140
|
def normalize_name_for_api(name: str) -> str:
|
|
143
141
|
"""
|
|
144
|
-
Normalizes names to the REST API convention.
|
|
142
|
+
Normalizes names to the REST API convention (with dashes instead of underscores).
|
|
145
143
|
|
|
146
144
|
Arguments:
|
|
147
145
|
name: The name to normalize.
|
|
@@ -196,8 +194,10 @@ def process_if_not_none(input_val: Optional[X], processor: Callable[[X], Y]) ->
|
|
|
196
194
|
return processor(input_val)
|
|
197
195
|
|
|
198
196
|
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
def _read_duckdb_init_sql(
|
|
198
|
+
*,
|
|
199
|
+
datalake_db_path: str | None = None,
|
|
200
|
+
) -> str:
|
|
201
201
|
"""
|
|
202
202
|
Reads and caches the duckdb init file content.
|
|
203
203
|
Returns None if file doesn't exist or is empty.
|
|
@@ -212,35 +212,38 @@ def _read_duckdb_init_sql() -> tuple[str, Path | None]:
|
|
|
212
212
|
if Path(c.DUCKDB_INIT_FILE).exists():
|
|
213
213
|
with open(c.DUCKDB_INIT_FILE, 'r') as f:
|
|
214
214
|
init_contents.append(f.read())
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return init_sql
|
|
215
|
+
|
|
216
|
+
if datalake_db_path:
|
|
217
|
+
attach_stmt = f"ATTACH '{datalake_db_path}' AS vdl (READ_ONLY);"
|
|
218
|
+
init_contents.append(attach_stmt)
|
|
219
|
+
use_stmt = f"USE vdl;"
|
|
220
|
+
init_contents.append(use_stmt)
|
|
221
|
+
|
|
222
|
+
init_sql = "\n\n".join(init_contents).strip()
|
|
223
|
+
return init_sql
|
|
224
224
|
except Exception as e:
|
|
225
225
|
raise ConfigurationError(f"Failed to read {c.DUCKDB_INIT_FILE}: {str(e)}") from e
|
|
226
226
|
|
|
227
|
-
def create_duckdb_connection(
|
|
227
|
+
def create_duckdb_connection(
|
|
228
|
+
db_path: str | Path = ":memory:",
|
|
229
|
+
*,
|
|
230
|
+
datalake_db_path: str | None = None
|
|
231
|
+
) -> duckdb.DuckDBPyConnection:
|
|
228
232
|
"""
|
|
229
233
|
Creates a DuckDB connection and initializes it with statements from duckdb init file
|
|
230
234
|
|
|
231
235
|
Arguments:
|
|
232
236
|
filepath: Path to the DuckDB database file. Defaults to in-memory database.
|
|
233
|
-
|
|
237
|
+
datalake_db_path: The path to the VDL catalog database if applicable. If exists, this is attached as 'vdl' (READ_ONLY). Default is None.
|
|
234
238
|
|
|
235
239
|
Returns:
|
|
236
240
|
A DuckDB connection (which must be closed after use)
|
|
237
241
|
"""
|
|
238
|
-
conn = duckdb.connect(
|
|
242
|
+
conn = duckdb.connect(db_path)
|
|
239
243
|
|
|
240
244
|
try:
|
|
241
|
-
init_sql
|
|
242
|
-
|
|
243
|
-
conn.execute(init_sql)
|
|
245
|
+
init_sql = _read_duckdb_init_sql(datalake_db_path=datalake_db_path)
|
|
246
|
+
conn.execute(init_sql)
|
|
244
247
|
except Exception as e:
|
|
245
248
|
conn.close()
|
|
246
249
|
raise ConfigurationError(f"Failed to execute {c.DUCKDB_INIT_FILE}: {str(e)}") from e
|
|
@@ -284,13 +287,20 @@ def load_yaml_config(filepath: FilePath) -> dict:
|
|
|
284
287
|
"""
|
|
285
288
|
try:
|
|
286
289
|
with open(filepath, 'r') as f:
|
|
287
|
-
|
|
290
|
+
content = yaml.safe_load(f)
|
|
291
|
+
content = content if content else {}
|
|
292
|
+
|
|
293
|
+
if not isinstance(content, dict):
|
|
294
|
+
raise yaml.YAMLError(f"Parsed content from YAML file must be a dictionary. Got: {content}")
|
|
295
|
+
|
|
296
|
+
return content
|
|
288
297
|
except yaml.YAMLError as e:
|
|
289
298
|
raise ConfigurationError(f"Failed to parse yaml file: {filepath}") from e
|
|
290
299
|
|
|
291
300
|
|
|
292
301
|
def run_duckdb_stmt(
|
|
293
|
-
logger: Logger, duckdb_conn: duckdb.DuckDBPyConnection, stmt: str, *, params: dict[str, Any] | None = None,
|
|
302
|
+
logger: Logger, duckdb_conn: duckdb.DuckDBPyConnection, stmt: str, *, params: dict[str, Any] | None = None,
|
|
303
|
+
model_name: str | None = None, redacted_values: list[str] = []
|
|
294
304
|
) -> duckdb.DuckDBPyConnection:
|
|
295
305
|
"""
|
|
296
306
|
Runs a statement on a DuckDB connection
|
|
@@ -306,7 +316,8 @@ def run_duckdb_stmt(
|
|
|
306
316
|
for value in redacted_values:
|
|
307
317
|
redacted_stmt = redacted_stmt.replace(value, "[REDACTED]")
|
|
308
318
|
|
|
309
|
-
|
|
319
|
+
for_model_name = f" for model '{model_name}'" if model_name is not None else ""
|
|
320
|
+
logger.debug(f"Running SQL statement{for_model_name}:\n{redacted_stmt}")
|
|
310
321
|
try:
|
|
311
322
|
return duckdb_conn.execute(stmt, params)
|
|
312
323
|
except duckdb.ParserException as e:
|
|
@@ -357,3 +368,74 @@ async def asyncio_gather(coroutines: list):
|
|
|
357
368
|
# Wait for tasks to be cancelled
|
|
358
369
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
359
370
|
raise
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def hash_string(input_str: str, salt: str) -> str:
|
|
374
|
+
"""
|
|
375
|
+
Hashes a string using SHA-256
|
|
376
|
+
"""
|
|
377
|
+
return hashlib.sha256((input_str + salt).encode()).hexdigest()
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
T = TypeVar('T')
|
|
381
|
+
def call_func(func: Callable[..., T], **kwargs) -> T:
|
|
382
|
+
"""
|
|
383
|
+
Calls a function with the given arguments if func expects arguments, otherwise calls func without arguments
|
|
384
|
+
"""
|
|
385
|
+
sig = inspect.signature(func)
|
|
386
|
+
# Filter kwargs to only include parameters that the function accepts
|
|
387
|
+
filtered_kwargs = {k: v for k, v in kwargs.items() if k in sig.parameters}
|
|
388
|
+
return func(**filtered_kwargs)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def generate_pkce_challenge(code_verifier: str) -> str:
|
|
392
|
+
"""Generate PKCE code challenge from code verifier"""
|
|
393
|
+
# Generate SHA256 hash of code_verifier
|
|
394
|
+
verifier_hash = hashlib.sha256(code_verifier.encode('utf-8')).digest()
|
|
395
|
+
# Base64 URL encode (without padding)
|
|
396
|
+
expected_challenge = base64.urlsafe_b64encode(verifier_hash).decode('utf-8').rstrip('=')
|
|
397
|
+
return expected_challenge
|
|
398
|
+
|
|
399
|
+
def validate_pkce_challenge(code_verifier: str, code_challenge: str) -> bool:
|
|
400
|
+
"""Validate PKCE code verifier against code challenge"""
|
|
401
|
+
# Generate expected challenge
|
|
402
|
+
expected_challenge = generate_pkce_challenge(code_verifier)
|
|
403
|
+
return expected_challenge == code_challenge
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def get_scheme(hostname: str | None) -> str:
|
|
407
|
+
"""Get the scheme of the request"""
|
|
408
|
+
return "http" if hostname in ("localhost", "127.0.0.1") else "https"
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def to_title_case(input_str: str) -> str:
|
|
412
|
+
"""Convert a string to title case"""
|
|
413
|
+
spaced_str = input_str.replace('_', ' ').replace('-', ' ')
|
|
414
|
+
return spaced_str.title()
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def to_bool(val: object) -> bool:
|
|
418
|
+
"""Convert common truthy/falsey representations to a boolean.
|
|
419
|
+
|
|
420
|
+
Accepted truthy values (case-insensitive): "1", "true", "t", "yes", "y", "on".
|
|
421
|
+
All other values are considered falsey. None is falsey.
|
|
422
|
+
"""
|
|
423
|
+
if isinstance(val, bool):
|
|
424
|
+
return val
|
|
425
|
+
if val is None:
|
|
426
|
+
return False
|
|
427
|
+
s = str(val).strip().lower()
|
|
428
|
+
return s in ("1", "true", "t", "yes", "y", "on")
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
ACCESS_LEVEL = Literal["admin", "member", "guest"]
|
|
432
|
+
|
|
433
|
+
def get_access_level_rank(access_level: ACCESS_LEVEL) -> int:
|
|
434
|
+
"""Get the rank of an access level. Lower ranks have more privileges."""
|
|
435
|
+
return { "admin": 1, "member": 2, "guest": 3 }.get(access_level.lower(), 1)
|
|
436
|
+
|
|
437
|
+
def user_has_elevated_privileges(user_access_level: ACCESS_LEVEL, required_access_level: ACCESS_LEVEL) -> bool:
|
|
438
|
+
"""Check if a user has privilege to access a resource"""
|
|
439
|
+
user_access_level_rank = get_access_level_rank(user_access_level)
|
|
440
|
+
required_access_level_rank = get_access_level_rank(required_access_level)
|
|
441
|
+
return user_access_level_rank <= required_access_level_rank
|
squirrels/_version.py
CHANGED
squirrels/arguments.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from ._arguments.init_time_args import ConnectionsArgs, AuthProviderArgs, ParametersArgs, BuildModelArgs
|
|
2
|
+
from ._arguments.run_time_args import ContextArgs, ModelArgs, DashboardArgs
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"ConnectionsArgs", "AuthProviderArgs", "ParametersArgs", "BuildModelArgs",
|
|
6
|
+
"ContextArgs", "ModelArgs", "DashboardArgs"
|
|
7
|
+
]
|
squirrels/auth.py
ADDED
squirrels/connections.py
ADDED
squirrels/dashboards.py
CHANGED
|
@@ -1,82 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
from ._dashboards import PngDashboard, HtmlDashboard
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Dashboard(metaclass=_abc.ABCMeta):
|
|
7
|
-
"""
|
|
8
|
-
Abstract parent class for all Dashboard classes.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
@property
|
|
12
|
-
@_abc.abstractmethod
|
|
13
|
-
def _content(self) -> bytes | str:
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
@property
|
|
17
|
-
@_abc.abstractmethod
|
|
18
|
-
def _format(self) -> str:
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class PngDashboard(Dashboard):
|
|
23
|
-
"""
|
|
24
|
-
Instantiate a Dashboard in PNG format from a matplotlib figure or bytes
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
def __init__(self, content: _figure.Figure | _io.BytesIO | bytes) -> None:
|
|
28
|
-
"""
|
|
29
|
-
Constructor for PngDashboard
|
|
30
|
-
|
|
31
|
-
Arguments:
|
|
32
|
-
content: The content of the dashboard as a matplotlib.figure.Figure or bytes
|
|
33
|
-
"""
|
|
34
|
-
if isinstance(content, _figure.Figure):
|
|
35
|
-
buffer = _io.BytesIO()
|
|
36
|
-
content.savefig(buffer, format=c.PNG)
|
|
37
|
-
content = buffer.getvalue()
|
|
38
|
-
|
|
39
|
-
if isinstance(content, _io.BytesIO):
|
|
40
|
-
content = content.getvalue()
|
|
41
|
-
|
|
42
|
-
self.__content = content
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def _content(self) -> bytes:
|
|
46
|
-
return self.__content
|
|
47
|
-
|
|
48
|
-
@property
|
|
49
|
-
def _format(self) -> _t.Literal['png']:
|
|
50
|
-
return c.PNG
|
|
51
|
-
|
|
52
|
-
def _repr_png_(self):
|
|
53
|
-
return self._content
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class HtmlDashboard(Dashboard):
|
|
57
|
-
"""
|
|
58
|
-
Instantiate a Dashboard from an HTML string
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
def __init__(self, content: _io.StringIO | str) -> None:
|
|
62
|
-
"""
|
|
63
|
-
Constructor for HtmlDashboard
|
|
64
|
-
|
|
65
|
-
Arguments:
|
|
66
|
-
content: The content of the dashboard as HTML string
|
|
67
|
-
"""
|
|
68
|
-
if isinstance(content, _io.StringIO):
|
|
69
|
-
content = content.getvalue()
|
|
70
|
-
|
|
71
|
-
self.__content = content
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def _content(self) -> str:
|
|
75
|
-
return self.__content
|
|
76
|
-
|
|
77
|
-
@property
|
|
78
|
-
def _format(self) -> _t.Literal['html']:
|
|
79
|
-
return c.HTML
|
|
80
|
-
|
|
81
|
-
def _repr_html_(self):
|
|
82
|
-
return self._content
|
|
3
|
+
__all__ = ["PngDashboard", "HtmlDashboard"]
|