mainsequence 4.0.14__tar.gz → 4.0.16__tar.gz
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.
- {mainsequence-4.0.14 → mainsequence-4.0.16}/PKG-INFO +1 -1
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/data_sources_interfaces/duckdb.py +18 -58
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/data_sources_interfaces/sqlite.py +17 -10
- mainsequence-4.0.16/mainsequence/client/dtype_codec.py +502 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/models_metatables.py +113 -2
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/models_tdag.py +134 -101
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/utils.py +3 -4
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/build_operations.py +16 -2
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/data_nodes.py +55 -34
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/persist_managers.py +16 -12
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/run_operations.py +64 -43
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/meta_tables/compiled_sql.py +20 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/meta_tables/sqlalchemy_contracts.py +3 -42
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence.egg-info/SOURCES.txt +1 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/pyproject.toml +1 -1
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_data_access_mixin_dimension_audit.py +71 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_data_node_search_join_filters.py +0 -1
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_data_node_storage_dimension_queries.py +2 -3
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_data_node_update_flow.py +57 -2
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_meta_tables_client_models.py +106 -1
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_meta_tables_sqlalchemy_contracts.py +45 -1
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_pod_project_resolution.py +50 -1
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_run_configuration.py +44 -1
- {mainsequence-4.0.14 → mainsequence-4.0.16}/LICENSE +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/README.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/__main__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/bootstrap.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/api.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/cli.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/config.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/cli/ui.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/base.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/client.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/command_center/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/command_center/app_component.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/command_center/connections.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/command_center/data_models.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/command_center/workspace.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/fastapi/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/fastapi/auth.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/models_user.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/compute_validation.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/defaults.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/logconf.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/__main__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/base_persist_managers.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/config.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/configuration_models.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/filters.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/models.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/namespacing.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/data_nodes/utils.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/filters.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/future_registry.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/meta_tables/__init__.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/meta_tables/hashing.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/pydantic_metadata.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/tdag/utils.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/setup.cfg +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_auth_precedence.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_build_operations_hashing.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_cli.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_client.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_command_center_app_component_models.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_command_center_data_models.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_command_center_models.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_dependency_extras.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_filter_normalization.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_logconf.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_secret_client_model.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_update_statistics.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_update_uid_guards.py +0 -0
- {mainsequence-4.0.14 → mainsequence-4.0.16}/tests/test_workspace_snapshot.py +0 -0
{mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/data_sources_interfaces/duckdb.py
RENAMED
|
@@ -14,6 +14,14 @@ from pyarrow import fs
|
|
|
14
14
|
|
|
15
15
|
from mainsequence.logconf import logger as base_logger
|
|
16
16
|
|
|
17
|
+
from ..dtype_codec import (
|
|
18
|
+
TIMESTAMP_TZ,
|
|
19
|
+
backend_type_to_token,
|
|
20
|
+
pandas_dtype_to_token,
|
|
21
|
+
token_to_backend_type,
|
|
22
|
+
token_to_pandas_dtype,
|
|
23
|
+
token_to_pandas_series,
|
|
24
|
+
)
|
|
17
25
|
from ..utils import DataFrequency, UniqueIdentifierRangeMap
|
|
18
26
|
|
|
19
27
|
|
|
@@ -422,7 +430,11 @@ class DuckDBInterface:
|
|
|
422
430
|
|
|
423
431
|
# —— basic hygiene ——--------------------------------------------------
|
|
424
432
|
df = df.copy()
|
|
425
|
-
df[time_index_name] =
|
|
433
|
+
df[time_index_name] = token_to_pandas_series(
|
|
434
|
+
df[time_index_name],
|
|
435
|
+
TIMESTAMP_TZ,
|
|
436
|
+
is_time_index=True,
|
|
437
|
+
)
|
|
426
438
|
|
|
427
439
|
# —— derive partition columns ——---------------------------------------
|
|
428
440
|
partitions = self._partition_keys(df[time_index_name], data_frequency=data_frequency)
|
|
@@ -1364,23 +1376,7 @@ class DuckDBInterface:
|
|
|
1364
1376
|
}
|
|
1365
1377
|
for col, target_type in type_map.items():
|
|
1366
1378
|
try:
|
|
1367
|
-
|
|
1368
|
-
arr = df[col].values
|
|
1369
|
-
arr_ns = arr.astype("datetime64[ns]")
|
|
1370
|
-
df[col] = pd.Series(
|
|
1371
|
-
pd.DatetimeIndex(arr_ns, tz="UTC"),
|
|
1372
|
-
index=df.index,
|
|
1373
|
-
name=col,
|
|
1374
|
-
)
|
|
1375
|
-
elif target_type == "datetime64[ns]":
|
|
1376
|
-
df[col] = pd.to_datetime(df[col], errors="coerce")
|
|
1377
|
-
else:
|
|
1378
|
-
if isinstance(
|
|
1379
|
-
target_type, (pd.Int64Dtype, pd.BooleanDtype, pd.StringDtype)
|
|
1380
|
-
):
|
|
1381
|
-
df[col] = df[col].astype(target_type, errors="ignore")
|
|
1382
|
-
else:
|
|
1383
|
-
df[col] = df[col].astype(target_type, errors="ignore")
|
|
1379
|
+
df[col] = token_to_pandas_series(df[col], target_type)
|
|
1384
1380
|
except Exception as type_e:
|
|
1385
1381
|
logger.warning(
|
|
1386
1382
|
f"Could not coerce column '{col}' to type '{target_type}': {type_e}"
|
|
@@ -1530,15 +1526,8 @@ class DuckDBInterface:
|
|
|
1530
1526
|
"""
|
|
1531
1527
|
Minimal dtype → DuckDB mapping. Extend as needed.
|
|
1532
1528
|
"""
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
if pd.api.types.is_integer_dtype(dtype):
|
|
1536
|
-
return "BIGINT"
|
|
1537
|
-
if pd.api.types.is_float_dtype(dtype):
|
|
1538
|
-
return "DOUBLE"
|
|
1539
|
-
if pd.api.types.is_bool_dtype(dtype):
|
|
1540
|
-
return "BOOLEAN"
|
|
1541
|
-
return "VARCHAR"
|
|
1529
|
+
token = pandas_dtype_to_token(dtype, remote=False, allow_naive_datetime=True)
|
|
1530
|
+
return token_to_backend_type(token, "duckdb")
|
|
1542
1531
|
|
|
1543
1532
|
@staticmethod
|
|
1544
1533
|
def _duck_to_pandas(duck_type: str, data_frequency: DataFrequency):
|
|
@@ -1548,34 +1537,5 @@ class DuckDBInterface:
|
|
|
1548
1537
|
`df.astype({...})` gets pandas’ nullable dtypes.
|
|
1549
1538
|
Extend as needed.
|
|
1550
1539
|
"""
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
# --- datetimes ------------------------------------------------------
|
|
1554
|
-
if dt in ("TIMESTAMPTZ", "TIMESTAMP WITH TIME ZONE"):
|
|
1555
|
-
# keep the UTC tz-awareness
|
|
1556
|
-
return "datetime64[ns, UTC]"
|
|
1557
|
-
|
|
1558
|
-
if dt in (
|
|
1559
|
-
"TIMESTAMP",
|
|
1560
|
-
"DATETIME",
|
|
1561
|
-
):
|
|
1562
|
-
# keep timezone if present; duckdb returns tz‑aware objects already,
|
|
1563
|
-
# so no explicit 'UTC' suffix is needed here.
|
|
1564
|
-
return "datetime64[ns]"
|
|
1565
|
-
if dt == "DATE":
|
|
1566
|
-
return "datetime64[ns]" # pandas treats it as midnight
|
|
1567
|
-
|
|
1568
|
-
# --- integers -------------------------------------------------------
|
|
1569
|
-
if dt in ("TINYINT", "SMALLINT", "INTEGER", "INT", "BIGINT"):
|
|
1570
|
-
return pd.Int64Dtype() # nullable 64‑bit int
|
|
1571
|
-
|
|
1572
|
-
# --- floats / numerics ---------------------------------------------
|
|
1573
|
-
if dt in ("REAL", "FLOAT", "DOUBLE", "DECIMAL"):
|
|
1574
|
-
return "float64"
|
|
1575
|
-
|
|
1576
|
-
# --- booleans -------------------------------------------------------
|
|
1577
|
-
if dt == "BOOLEAN":
|
|
1578
|
-
return pd.BooleanDtype() # nullable boolean
|
|
1579
|
-
|
|
1580
|
-
# --- everything else ------------------------------------------------
|
|
1581
|
-
return pd.StringDtype() # pandas‘ native nullable string
|
|
1540
|
+
token = backend_type_to_token(duck_type, "duckdb", allow_naive_datetime=True)
|
|
1541
|
+
return token_to_pandas_dtype(token)
|
{mainsequence-4.0.14 → mainsequence-4.0.16}/mainsequence/client/data_sources_interfaces/sqlite.py
RENAMED
|
@@ -10,6 +10,12 @@ import pandas as pd
|
|
|
10
10
|
|
|
11
11
|
from mainsequence.logconf import logger as base_logger
|
|
12
12
|
|
|
13
|
+
from ..dtype_codec import (
|
|
14
|
+
TIMESTAMP_TZ,
|
|
15
|
+
pandas_dtype_to_token,
|
|
16
|
+
token_to_backend_type,
|
|
17
|
+
token_to_pandas_series,
|
|
18
|
+
)
|
|
13
19
|
from ..utils import UniqueIdentifierRangeMap
|
|
14
20
|
|
|
15
21
|
|
|
@@ -78,15 +84,12 @@ class SQLiteInterface:
|
|
|
78
84
|
|
|
79
85
|
@staticmethod
|
|
80
86
|
def _sqlite_type(series: pd.Series, *, is_time_index: bool = False) -> str:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if pd.api.types.is_float_dtype(series):
|
|
88
|
-
return "REAL"
|
|
89
|
-
return "TEXT"
|
|
87
|
+
token = (
|
|
88
|
+
TIMESTAMP_TZ
|
|
89
|
+
if is_time_index
|
|
90
|
+
else pandas_dtype_to_token(series.dtype, remote=False, allow_naive_datetime=True)
|
|
91
|
+
)
|
|
92
|
+
return token_to_backend_type(token, "sqlite")
|
|
90
93
|
|
|
91
94
|
@staticmethod
|
|
92
95
|
def _sqlite_value(value: Any) -> Any:
|
|
@@ -442,7 +445,11 @@ class SQLiteInterface:
|
|
|
442
445
|
|
|
443
446
|
df = pd.read_sql_query(query, self.con, params=params)
|
|
444
447
|
if not df.empty and time_index_name in df.columns:
|
|
445
|
-
df[time_index_name] =
|
|
448
|
+
df[time_index_name] = token_to_pandas_series(
|
|
449
|
+
df[time_index_name],
|
|
450
|
+
TIMESTAMP_TZ,
|
|
451
|
+
is_time_index=True,
|
|
452
|
+
)
|
|
446
453
|
return df
|
|
447
454
|
|
|
448
455
|
def time_index_minima(
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import math
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
DATE = "date"
|
|
14
|
+
TIMESTAMP_TZ = "timestamp with time zone"
|
|
15
|
+
LOCAL_DATETIME_NAIVE = "datetime64[ns]"
|
|
16
|
+
STRING = "string"
|
|
17
|
+
UUID_TOKEN = "uuid"
|
|
18
|
+
JSON = "json"
|
|
19
|
+
JSONB = "jsonb"
|
|
20
|
+
BOOL = "bool"
|
|
21
|
+
INT16 = "int16"
|
|
22
|
+
INT32 = "int32"
|
|
23
|
+
INT64 = "int64"
|
|
24
|
+
FLOAT32 = "float32"
|
|
25
|
+
FLOAT64 = "float64"
|
|
26
|
+
NUMERIC = "numeric"
|
|
27
|
+
|
|
28
|
+
_OFFSET_RE = re.compile(r"(z|[+-]\d{2}:?\d{2})$", re.IGNORECASE)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_nullish(value: Any) -> bool:
|
|
32
|
+
if value is None:
|
|
33
|
+
return True
|
|
34
|
+
try:
|
|
35
|
+
if value is pd.NaT:
|
|
36
|
+
return True
|
|
37
|
+
if isinstance(value, float) and math.isnan(value):
|
|
38
|
+
return True
|
|
39
|
+
result = pd.isna(value)
|
|
40
|
+
except (TypeError, ValueError):
|
|
41
|
+
return False
|
|
42
|
+
if isinstance(result, (bool, np.bool_)):
|
|
43
|
+
return bool(result)
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def normalize_dtype_token(
|
|
48
|
+
value: Any,
|
|
49
|
+
*,
|
|
50
|
+
remote: bool = True,
|
|
51
|
+
allow_naive_datetime: bool = False,
|
|
52
|
+
) -> str:
|
|
53
|
+
token = str(value or "").strip()
|
|
54
|
+
if not token:
|
|
55
|
+
raise ValueError("DType token is required.")
|
|
56
|
+
|
|
57
|
+
lowered = " ".join(token.replace("_", " ").strip().lower().split())
|
|
58
|
+
compact = lowered.replace(" ", "")
|
|
59
|
+
|
|
60
|
+
if lowered in {"date", "datetime.date"}:
|
|
61
|
+
return DATE
|
|
62
|
+
|
|
63
|
+
if lowered in {
|
|
64
|
+
"datetime",
|
|
65
|
+
"datetime.datetime",
|
|
66
|
+
"datetime64[ns, utc]",
|
|
67
|
+
"timestamp with time zone",
|
|
68
|
+
"timestamptz",
|
|
69
|
+
}:
|
|
70
|
+
return TIMESTAMP_TZ
|
|
71
|
+
|
|
72
|
+
if lowered in {
|
|
73
|
+
"datetime64[ns]",
|
|
74
|
+
"timestamp",
|
|
75
|
+
"timestamp without time zone",
|
|
76
|
+
"datetime without time zone",
|
|
77
|
+
"datetime naive",
|
|
78
|
+
}:
|
|
79
|
+
if remote and not allow_naive_datetime:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"Timezone-naive datetime dtypes are local-backend-only. "
|
|
82
|
+
"Remote writes require 'timestamp with time zone'."
|
|
83
|
+
)
|
|
84
|
+
return LOCAL_DATETIME_NAIVE
|
|
85
|
+
|
|
86
|
+
if lowered in {"object", "str", "string", "text", "varchar", "char", "unicode"}:
|
|
87
|
+
return STRING
|
|
88
|
+
if "varchar" in lowered:
|
|
89
|
+
return STRING
|
|
90
|
+
if compact in {"uuid", "uuidtype", "uuid_", "postgresqluuid"}:
|
|
91
|
+
return UUID_TOKEN
|
|
92
|
+
if lowered in {"json"}:
|
|
93
|
+
return JSON
|
|
94
|
+
if lowered in {"jsonb"}:
|
|
95
|
+
return JSONB
|
|
96
|
+
if lowered in {"bool", "boolean"}:
|
|
97
|
+
return BOOL
|
|
98
|
+
if lowered in {"int16", "smallint", "int2"}:
|
|
99
|
+
return INT16
|
|
100
|
+
if lowered in {"int32", "integer", "int", "int4"}:
|
|
101
|
+
return INT32
|
|
102
|
+
if lowered in {"int64", "biginteger", "bigint", "int8"}:
|
|
103
|
+
return INT64
|
|
104
|
+
if lowered in {"uint16", "uint32", "uint64"}:
|
|
105
|
+
return INT64
|
|
106
|
+
if lowered in {"float32", "real", "float4"}:
|
|
107
|
+
return FLOAT32
|
|
108
|
+
if lowered in {"float", "float64", "double", "double precision", "float8"}:
|
|
109
|
+
return FLOAT64
|
|
110
|
+
if lowered == "numeric" or lowered.startswith("numeric") or lowered == "decimal":
|
|
111
|
+
return NUMERIC
|
|
112
|
+
|
|
113
|
+
return token
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def normalize_column_dtypes_map(
|
|
117
|
+
column_dtypes_map: Mapping[str, Any],
|
|
118
|
+
*,
|
|
119
|
+
remote: bool = True,
|
|
120
|
+
allow_naive_datetime: bool = False,
|
|
121
|
+
) -> dict[str, str]:
|
|
122
|
+
return {
|
|
123
|
+
str(column_name): normalize_dtype_token(
|
|
124
|
+
dtype,
|
|
125
|
+
remote=remote,
|
|
126
|
+
allow_naive_datetime=allow_naive_datetime,
|
|
127
|
+
)
|
|
128
|
+
for column_name, dtype in dict(column_dtypes_map).items()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _record_definition_field(record: Any, field_name: str) -> Any:
|
|
133
|
+
if isinstance(record, Mapping):
|
|
134
|
+
return record.get(field_name)
|
|
135
|
+
return getattr(record, field_name, None)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def record_definitions_to_column_dtypes_map(
|
|
139
|
+
records: Any,
|
|
140
|
+
*,
|
|
141
|
+
remote: bool = True,
|
|
142
|
+
allow_naive_datetime: bool = False,
|
|
143
|
+
) -> dict[str, str]:
|
|
144
|
+
if not records:
|
|
145
|
+
return {}
|
|
146
|
+
|
|
147
|
+
column_dtypes_map: dict[str, str] = {}
|
|
148
|
+
duplicate_columns: set[str] = set()
|
|
149
|
+
for record in records:
|
|
150
|
+
column_name = _record_definition_field(record, "column_name")
|
|
151
|
+
dtype = _record_definition_field(record, "dtype")
|
|
152
|
+
if not column_name or not dtype:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"Record definitions must include non-empty 'column_name' and 'dtype' fields."
|
|
155
|
+
)
|
|
156
|
+
column_name = str(column_name)
|
|
157
|
+
if column_name in column_dtypes_map:
|
|
158
|
+
duplicate_columns.add(column_name)
|
|
159
|
+
column_dtypes_map[column_name] = normalize_dtype_token(
|
|
160
|
+
dtype,
|
|
161
|
+
remote=remote,
|
|
162
|
+
allow_naive_datetime=allow_naive_datetime,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if duplicate_columns:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"Duplicate DataNode record column names: {sorted(duplicate_columns)}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return column_dtypes_map
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def pandas_dtype_to_token(
|
|
174
|
+
dtype: Any,
|
|
175
|
+
*,
|
|
176
|
+
remote: bool = True,
|
|
177
|
+
allow_naive_datetime: bool = False,
|
|
178
|
+
) -> str:
|
|
179
|
+
if isinstance(dtype, pd.DatetimeTZDtype):
|
|
180
|
+
return TIMESTAMP_TZ
|
|
181
|
+
if pd.api.types.is_datetime64_any_dtype(dtype):
|
|
182
|
+
if remote and not allow_naive_datetime:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
"Timezone-naive pandas datetime64[ns] is local-backend-only. "
|
|
185
|
+
"Remote writes require timezone-aware datetime64[ns, UTC]."
|
|
186
|
+
)
|
|
187
|
+
return LOCAL_DATETIME_NAIVE
|
|
188
|
+
if pd.api.types.is_bool_dtype(dtype):
|
|
189
|
+
return BOOL
|
|
190
|
+
if pd.api.types.is_integer_dtype(dtype):
|
|
191
|
+
name = str(dtype).lower()
|
|
192
|
+
if "16" in name:
|
|
193
|
+
return INT16
|
|
194
|
+
if "32" in name:
|
|
195
|
+
return INT32
|
|
196
|
+
return INT64
|
|
197
|
+
if pd.api.types.is_float_dtype(dtype):
|
|
198
|
+
return FLOAT32 if "32" in str(dtype).lower() else FLOAT64
|
|
199
|
+
if pd.api.types.is_string_dtype(dtype):
|
|
200
|
+
return STRING
|
|
201
|
+
return STRING
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def pandas_dtypes_to_column_map(
|
|
205
|
+
dtypes: Mapping[str, Any],
|
|
206
|
+
*,
|
|
207
|
+
remote: bool = True,
|
|
208
|
+
allow_naive_datetime: bool = False,
|
|
209
|
+
) -> dict[str, str]:
|
|
210
|
+
return {
|
|
211
|
+
str(column_name): pandas_dtype_to_token(
|
|
212
|
+
dtype,
|
|
213
|
+
remote=remote,
|
|
214
|
+
allow_naive_datetime=allow_naive_datetime,
|
|
215
|
+
)
|
|
216
|
+
for column_name, dtype in dtypes.items()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def token_to_pandas_dtype(token: Any, *, nullable: bool = True) -> Any:
|
|
221
|
+
normalized = normalize_dtype_token(token, remote=False, allow_naive_datetime=True)
|
|
222
|
+
if normalized == STRING:
|
|
223
|
+
return "string"
|
|
224
|
+
if normalized == UUID_TOKEN:
|
|
225
|
+
return "string"
|
|
226
|
+
if normalized in {JSON, JSONB}:
|
|
227
|
+
return "object"
|
|
228
|
+
if normalized in {INT16, INT32, INT64}:
|
|
229
|
+
return "Int64" if nullable else normalized
|
|
230
|
+
if normalized == BOOL:
|
|
231
|
+
return "boolean" if nullable else "bool"
|
|
232
|
+
if normalized in {FLOAT32, FLOAT64, NUMERIC}:
|
|
233
|
+
return "float64"
|
|
234
|
+
if normalized == TIMESTAMP_TZ:
|
|
235
|
+
return "datetime64[ns, UTC]"
|
|
236
|
+
if normalized == LOCAL_DATETIME_NAIVE:
|
|
237
|
+
return "datetime64[ns]"
|
|
238
|
+
if normalized == DATE:
|
|
239
|
+
return "object"
|
|
240
|
+
return str(token)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def token_to_pandas_series(
|
|
244
|
+
series: pd.Series,
|
|
245
|
+
token: Any,
|
|
246
|
+
*,
|
|
247
|
+
is_time_index: bool = False,
|
|
248
|
+
nullable: bool = True,
|
|
249
|
+
) -> pd.Series:
|
|
250
|
+
normalized = TIMESTAMP_TZ if is_time_index else normalize_dtype_token(
|
|
251
|
+
token,
|
|
252
|
+
remote=False,
|
|
253
|
+
allow_naive_datetime=True,
|
|
254
|
+
)
|
|
255
|
+
if normalized == TIMESTAMP_TZ:
|
|
256
|
+
return pd.to_datetime(series, errors="coerce", utc=True)
|
|
257
|
+
if normalized == LOCAL_DATETIME_NAIVE:
|
|
258
|
+
parsed = pd.to_datetime(series, errors="coerce")
|
|
259
|
+
try:
|
|
260
|
+
if getattr(parsed.dt, "tz", None) is not None:
|
|
261
|
+
parsed = parsed.dt.tz_convert(None)
|
|
262
|
+
except (AttributeError, TypeError):
|
|
263
|
+
pass
|
|
264
|
+
return parsed
|
|
265
|
+
if normalized == DATE:
|
|
266
|
+
return pd.to_datetime(series, errors="coerce").dt.date
|
|
267
|
+
if normalized in {JSON, JSONB}:
|
|
268
|
+
return series
|
|
269
|
+
pandas_dtype = token_to_pandas_dtype(normalized, nullable=nullable)
|
|
270
|
+
if pandas_dtype == "object":
|
|
271
|
+
return series
|
|
272
|
+
return series.astype(pandas_dtype)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def is_temporal_token(token: Any) -> bool:
|
|
276
|
+
return normalize_dtype_token(token, remote=False, allow_naive_datetime=True) in {
|
|
277
|
+
DATE,
|
|
278
|
+
TIMESTAMP_TZ,
|
|
279
|
+
LOCAL_DATETIME_NAIVE,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _string_has_explicit_timezone(value: str) -> bool:
|
|
284
|
+
stripped = value.strip()
|
|
285
|
+
if not stripped:
|
|
286
|
+
return True
|
|
287
|
+
return bool(_OFFSET_RE.search(stripped))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _timestamp_is_timezone_aware(value: Any) -> bool:
|
|
291
|
+
if isinstance(value, pd.Timestamp):
|
|
292
|
+
return value.tzinfo is not None
|
|
293
|
+
if isinstance(value, datetime.datetime):
|
|
294
|
+
return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _serialize_date_value(value: Any) -> str | None:
|
|
299
|
+
if is_nullish(value):
|
|
300
|
+
return None
|
|
301
|
+
if isinstance(value, datetime.datetime):
|
|
302
|
+
return value.date().isoformat()
|
|
303
|
+
if isinstance(value, datetime.date):
|
|
304
|
+
return value.isoformat()
|
|
305
|
+
timestamp = pd.to_datetime(value, errors="raise")
|
|
306
|
+
if isinstance(timestamp, pd.Timestamp):
|
|
307
|
+
return timestamp.date().isoformat()
|
|
308
|
+
return str(value)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _serialize_timestamp_tz_value(value: Any) -> str | None:
|
|
312
|
+
if is_nullish(value):
|
|
313
|
+
return None
|
|
314
|
+
if isinstance(value, np.datetime64):
|
|
315
|
+
raise ValueError(
|
|
316
|
+
"Timezone-naive numpy datetime64 values cannot be sent to remote TS Manager."
|
|
317
|
+
)
|
|
318
|
+
if isinstance(value, str) and not _string_has_explicit_timezone(value):
|
|
319
|
+
raise ValueError(
|
|
320
|
+
f"Remote datetime value {value!r} must include an explicit timezone offset or Z."
|
|
321
|
+
)
|
|
322
|
+
if isinstance(value, (pd.Timestamp, datetime.datetime)) and not _timestamp_is_timezone_aware(value):
|
|
323
|
+
raise ValueError(
|
|
324
|
+
f"Remote datetime value {value!r} must be timezone-aware."
|
|
325
|
+
)
|
|
326
|
+
timestamp = pd.to_datetime(value, errors="raise", utc=True)
|
|
327
|
+
if not isinstance(timestamp, pd.Timestamp):
|
|
328
|
+
timestamp = pd.Timestamp(timestamp).tz_convert("UTC")
|
|
329
|
+
if timestamp.tzinfo is None:
|
|
330
|
+
raise ValueError(
|
|
331
|
+
f"Remote datetime value {value!r} must be timezone-aware."
|
|
332
|
+
)
|
|
333
|
+
timestamp = timestamp.tz_convert("UTC")
|
|
334
|
+
return timestamp.isoformat().replace("+00:00", "Z")
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def serialize_remote_value(value: Any, token: Any) -> Any:
|
|
338
|
+
normalized = normalize_dtype_token(token, remote=True)
|
|
339
|
+
if normalized == DATE:
|
|
340
|
+
return _serialize_date_value(value)
|
|
341
|
+
if normalized == TIMESTAMP_TZ:
|
|
342
|
+
return _serialize_timestamp_tz_value(value)
|
|
343
|
+
if is_nullish(value):
|
|
344
|
+
return None
|
|
345
|
+
if isinstance(value, UUID):
|
|
346
|
+
return str(value)
|
|
347
|
+
return value
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def serialize_remote_parameters(
|
|
351
|
+
parameters: Mapping[str, Any] | list[Any],
|
|
352
|
+
parameter_types: Mapping[str, Any] | None,
|
|
353
|
+
) -> dict[str, Any] | list[Any]:
|
|
354
|
+
if not parameter_types or not isinstance(parameters, Mapping):
|
|
355
|
+
return dict(parameters) if isinstance(parameters, Mapping) else list(parameters)
|
|
356
|
+
normalized_types = normalize_column_dtypes_map(parameter_types, remote=True)
|
|
357
|
+
serialized = dict(parameters)
|
|
358
|
+
for name, token in normalized_types.items():
|
|
359
|
+
if name in serialized:
|
|
360
|
+
serialized[name] = serialize_remote_value(serialized[name], token)
|
|
361
|
+
return serialized
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def prepare_dataframe_for_remote_write(
|
|
365
|
+
df: pd.DataFrame,
|
|
366
|
+
*,
|
|
367
|
+
column_dtypes_map: Mapping[str, Any],
|
|
368
|
+
time_index_name: str,
|
|
369
|
+
) -> pd.DataFrame:
|
|
370
|
+
normalized_map = normalize_column_dtypes_map(column_dtypes_map, remote=True)
|
|
371
|
+
prepared = df.copy()
|
|
372
|
+
if time_index_name not in normalized_map:
|
|
373
|
+
normalized_map[time_index_name] = TIMESTAMP_TZ
|
|
374
|
+
for column_name, token in normalized_map.items():
|
|
375
|
+
if column_name not in prepared.columns:
|
|
376
|
+
continue
|
|
377
|
+
normalized = normalize_dtype_token(token, remote=True)
|
|
378
|
+
if normalized not in {DATE, TIMESTAMP_TZ}:
|
|
379
|
+
continue
|
|
380
|
+
prepared[column_name] = [
|
|
381
|
+
serialize_remote_value(value, normalized)
|
|
382
|
+
for value in prepared[column_name].tolist()
|
|
383
|
+
]
|
|
384
|
+
return prepared
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def token_to_backend_type(token: Any, backend: str) -> str:
|
|
388
|
+
normalized = normalize_dtype_token(token, remote=False, allow_naive_datetime=True)
|
|
389
|
+
backend_key = backend.strip().lower()
|
|
390
|
+
if backend_key == "sqlite":
|
|
391
|
+
if normalized in {INT16, INT32, INT64, BOOL}:
|
|
392
|
+
return "INTEGER"
|
|
393
|
+
if normalized in {FLOAT32, FLOAT64, NUMERIC}:
|
|
394
|
+
return "REAL"
|
|
395
|
+
return "TEXT"
|
|
396
|
+
if backend_key in {"duckdb", "duck_db"}:
|
|
397
|
+
if normalized == DATE:
|
|
398
|
+
return "DATE"
|
|
399
|
+
if normalized == TIMESTAMP_TZ:
|
|
400
|
+
return "TIMESTAMPTZ"
|
|
401
|
+
if normalized == LOCAL_DATETIME_NAIVE:
|
|
402
|
+
return "TIMESTAMP"
|
|
403
|
+
if normalized in {INT16, INT32, INT64}:
|
|
404
|
+
return "BIGINT"
|
|
405
|
+
if normalized in {FLOAT32, FLOAT64, NUMERIC}:
|
|
406
|
+
return "DOUBLE"
|
|
407
|
+
if normalized == BOOL:
|
|
408
|
+
return "BOOLEAN"
|
|
409
|
+
return "VARCHAR"
|
|
410
|
+
raise ValueError(f"Unsupported dtype backend {backend!r}.")
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def backend_type_to_token(
|
|
414
|
+
backend_type: Any,
|
|
415
|
+
backend: str,
|
|
416
|
+
*,
|
|
417
|
+
allow_naive_datetime: bool = True,
|
|
418
|
+
) -> str:
|
|
419
|
+
raw = str(backend_type or "").strip().upper()
|
|
420
|
+
backend_key = backend.strip().lower()
|
|
421
|
+
if backend_key == "sqlite":
|
|
422
|
+
if raw == "INTEGER":
|
|
423
|
+
return INT64
|
|
424
|
+
if raw == "REAL":
|
|
425
|
+
return FLOAT64
|
|
426
|
+
return STRING
|
|
427
|
+
if backend_key in {"duckdb", "duck_db"}:
|
|
428
|
+
if raw in {"TIMESTAMPTZ", "TIMESTAMP WITH TIME ZONE"}:
|
|
429
|
+
return TIMESTAMP_TZ
|
|
430
|
+
if raw in {"TIMESTAMP", "DATETIME"}:
|
|
431
|
+
return normalize_dtype_token(
|
|
432
|
+
LOCAL_DATETIME_NAIVE,
|
|
433
|
+
remote=False,
|
|
434
|
+
allow_naive_datetime=allow_naive_datetime,
|
|
435
|
+
)
|
|
436
|
+
if raw == "DATE":
|
|
437
|
+
return DATE
|
|
438
|
+
if raw in {"TINYINT", "SMALLINT", "INTEGER", "INT", "BIGINT"}:
|
|
439
|
+
return INT64
|
|
440
|
+
if raw in {"REAL", "FLOAT", "DOUBLE", "DECIMAL"}:
|
|
441
|
+
return FLOAT64
|
|
442
|
+
if raw == "BOOLEAN":
|
|
443
|
+
return BOOL
|
|
444
|
+
return STRING
|
|
445
|
+
return normalize_dtype_token(str(backend_type), remote=False, allow_naive_datetime=True)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def sqlalchemy_type_to_token(column_type: Any, *, remote: bool = True) -> str:
|
|
449
|
+
type_name = type(column_type).__name__.lower()
|
|
450
|
+
backend_type = str(column_type).upper()
|
|
451
|
+
normalized_backend = " ".join(backend_type.lower().split())
|
|
452
|
+
timezone = getattr(column_type, "timezone", None)
|
|
453
|
+
|
|
454
|
+
if type_name in {"datetime"} or "timestamp" in normalized_backend:
|
|
455
|
+
if timezone is False or "without time zone" in normalized_backend:
|
|
456
|
+
if remote:
|
|
457
|
+
raise ValueError(
|
|
458
|
+
"Timezone-naive SQLAlchemy DateTime columns are not supported "
|
|
459
|
+
"by the remote TS Manager contract."
|
|
460
|
+
)
|
|
461
|
+
return LOCAL_DATETIME_NAIVE
|
|
462
|
+
if timezone is True or "with time zone" in normalized_backend or "timestamptz" in normalized_backend:
|
|
463
|
+
return TIMESTAMP_TZ
|
|
464
|
+
if remote:
|
|
465
|
+
raise ValueError(
|
|
466
|
+
"SQLAlchemy DateTime columns must set timezone=True for remote MetaTables."
|
|
467
|
+
)
|
|
468
|
+
return LOCAL_DATETIME_NAIVE
|
|
469
|
+
|
|
470
|
+
if type_name in {"date"} or normalized_backend == "date":
|
|
471
|
+
return DATE
|
|
472
|
+
if type_name in {"uuid", "uuidtype", "uuid_", "postgresqluuid"} or normalized_backend == "uuid":
|
|
473
|
+
return UUID_TOKEN
|
|
474
|
+
if type_name in {"string", "unicode", "text", "varchar", "char"}:
|
|
475
|
+
return STRING
|
|
476
|
+
if type_name in {"integer", "biginteger"}:
|
|
477
|
+
return INT64
|
|
478
|
+
if type_name in {"smallinteger"}:
|
|
479
|
+
return INT16
|
|
480
|
+
if type_name in {"float", "double", "doubleprecision"}:
|
|
481
|
+
return FLOAT64
|
|
482
|
+
if type_name in {"numeric", "decimal"}:
|
|
483
|
+
return NUMERIC
|
|
484
|
+
if type_name in {"boolean"}:
|
|
485
|
+
return BOOL
|
|
486
|
+
if type_name in {"json", "jsonb"}:
|
|
487
|
+
return JSONB if type_name == "jsonb" else JSON
|
|
488
|
+
return normalize_dtype_token(backend_type, remote=remote)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def sqlalchemy_backend_type(column_type: Any, token: Any) -> str:
|
|
492
|
+
normalized = normalize_dtype_token(token, remote=False, allow_naive_datetime=True)
|
|
493
|
+
if normalized == UUID_TOKEN:
|
|
494
|
+
return "UUID"
|
|
495
|
+
backend_type = str(column_type).upper()
|
|
496
|
+
if normalized == TIMESTAMP_TZ:
|
|
497
|
+
return "TIMESTAMP WITH TIME ZONE"
|
|
498
|
+
if normalized == LOCAL_DATETIME_NAIVE:
|
|
499
|
+
return "TIMESTAMP WITHOUT TIME ZONE"
|
|
500
|
+
if normalized == DATE:
|
|
501
|
+
return "DATE"
|
|
502
|
+
return backend_type or normalized
|