boti-data 1.3.4__tar.gz → 1.3.6__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.
- {boti_data-1.3.4 → boti_data-1.3.6}/PKG-INFO +6 -7
- {boti_data-1.3.4 → boti_data-1.3.6}/README.md +5 -6
- {boti_data-1.3.4 → boti_data-1.3.6}/pyproject.toml +1 -1
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/datacube/contract.py +2 -2
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/partitioned_types.py +2 -2
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_model_registry.py +7 -2
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/enrichment/transformers.py +2 -2
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/load_request.py +5 -5
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/post_process.py +1 -1
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/requests.py +3 -3
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/sql_strategy_configured_async.py +2 -2
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/helper.py +4 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data.egg-info/PKG-INFO +6 -7
- {boti_data-1.3.4 → boti_data-1.3.6}/setup.cfg +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/arrow_schema.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/connection_catalog.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/datacube/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/datacube/artifact.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/datacube/base.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/datacube/resource.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/dataset/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/dataset/hybrid.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/dataset/hybrid_support.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/arrow_schema_mapper.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/engine_registry.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/partitioned_execution.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/partitioned_loader.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/partitioned_planner.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/partitioned_planner_async.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/partitioned_planning_math.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/partitioned_row_materialization.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/partitioned_statements.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_config.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_engine.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_manager.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_model_builder.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_model_module_injection.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_model_registry_types.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_readonly.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_repository.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sql_resource.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/sqlalchemy_async.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/db/vector_search.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/enrichment/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/enrichment/async_enricher.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/enrichment/composite.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/enrichment/protocol.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/enrichment/specs.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/field_map.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/arrow_kernels.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/arrow_kernels_comparison.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/arrow_kernels_compile.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/arrow_kernels_string.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/backend_ops.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/expressions.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/handler.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/parquet_compiler.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/utils.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/filters/value_parsing.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/_backend_strategies.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/_factory.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/_gateway_init.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/_payloads.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/_series_filters.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/arrow_adapters.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/build_context.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/chunking.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/configured_load.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/core.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/core_describe.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/core_load.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/datacube_strategy.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/frame_convert.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/frame_strategies.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/incremental.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/load_execution.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/loaders.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/normalization.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/parquet_strategy.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/planning.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/policies.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/return_type.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/select_cache.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/semi_join.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/sql_describe.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/sql_guard.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/sql_partitioned_exec.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/sql_size_estimation.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/gateway/sql_strategy.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/joins.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/config.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/discovery.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/file_info.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/filesystem.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/reader.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/reader_config.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/resource.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/parquet/schema_filters.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/pipelines/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/pipelines/base.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/pipelines/parquet_pipeline.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/pipelines/registry.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/pipelines/sinks.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/pipelines/sinks_common.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/pipelines/sinks_parquet.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/pipelines/sinks_text.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/py.typed +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/schema.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/watermark/__init__.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/watermark/incremental.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data/watermark/store.py +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data.egg-info/SOURCES.txt +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data.egg-info/dependency_links.txt +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data.egg-info/requires.txt +0 -0
- {boti_data-1.3.4 → boti_data-1.3.6}/src/boti_data.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boti-data
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.6
|
|
4
4
|
Summary: Data infrastructure for the Boti ecosystem
|
|
5
5
|
Author: Luis Valverde
|
|
6
6
|
Maintainer-email: Luis Valverde <lvalverdeb@gmail.com>
|
|
@@ -610,13 +610,13 @@ with apply_recommended_dask_config():
|
|
|
610
610
|
|
|
611
611
|
### Dry-run and preview on gateway loads
|
|
612
612
|
|
|
613
|
-
Use `dry_run=True` to build and inspect a lazy Dask load graph without materializing it, and `
|
|
613
|
+
Use `dry_run=True` to build and inspect a lazy Dask load graph without materializing it, and `limit=` to safely sample rows inside the session:
|
|
614
614
|
|
|
615
615
|
```python
|
|
616
616
|
with DataHelper.session(scheduler_address="tcp://scheduler:8786", verify_connectivity=True) as client:
|
|
617
617
|
with DataHelper(config, table="transactions") as helper:
|
|
618
618
|
ddf = helper.dask.load(year=2024, dry_run=True, diagnostics=True)
|
|
619
|
-
|
|
619
|
+
sample = helper.dask.load(year=2024, limit=5).compute()
|
|
620
620
|
```
|
|
621
621
|
|
|
622
622
|
### Resilient joins
|
|
@@ -702,7 +702,7 @@ with DataHelper.session(scheduler_address="tcp://scheduler:8786") as client:
|
|
|
702
702
|
by_region = ddf.groupby("region").size().compute()
|
|
703
703
|
|
|
704
704
|
# Preview persisted data before the managed session closes.
|
|
705
|
-
|
|
705
|
+
sample = helper.load(year=2024, limit=5, persist=True).compute()
|
|
706
706
|
```
|
|
707
707
|
|
|
708
708
|
### Shared sessions for repeated notebook cells
|
|
@@ -996,8 +996,7 @@ with SqlDatabaseResource(config) as db:
|
|
|
996
996
|
from boti_data import DataGateway, SqlDatabaseConfig
|
|
997
997
|
|
|
998
998
|
gateway = DataGateway(
|
|
999
|
-
|
|
1000
|
-
config=SqlDatabaseConfig(connection_url="sqlite:///example.db", query_only=True),
|
|
999
|
+
SqlDatabaseConfig(connection_url="sqlite:///example.db", query_only=True),
|
|
1001
1000
|
)
|
|
1002
1001
|
```
|
|
1003
1002
|
|
|
@@ -1045,7 +1044,7 @@ config = SqlDatabaseConfig(
|
|
|
1045
1044
|
worker_connection_env_var="DB_URL",
|
|
1046
1045
|
)
|
|
1047
1046
|
|
|
1048
|
-
with DataHelper.session(cluster_factory=LocalCluster, n_workers
|
|
1047
|
+
with DataHelper.session(cluster_factory=LocalCluster, cluster_kwargs={"n_workers": 4}) as client:
|
|
1049
1048
|
with DataHelper(config, table="events") as helper:
|
|
1050
1049
|
ddf = helper.dask.load(event_type="purchase", persist=True)
|
|
1051
1050
|
result = ddf.groupby("user_id").size().compute()
|
|
@@ -575,13 +575,13 @@ with apply_recommended_dask_config():
|
|
|
575
575
|
|
|
576
576
|
### Dry-run and preview on gateway loads
|
|
577
577
|
|
|
578
|
-
Use `dry_run=True` to build and inspect a lazy Dask load graph without materializing it, and `
|
|
578
|
+
Use `dry_run=True` to build and inspect a lazy Dask load graph without materializing it, and `limit=` to safely sample rows inside the session:
|
|
579
579
|
|
|
580
580
|
```python
|
|
581
581
|
with DataHelper.session(scheduler_address="tcp://scheduler:8786", verify_connectivity=True) as client:
|
|
582
582
|
with DataHelper(config, table="transactions") as helper:
|
|
583
583
|
ddf = helper.dask.load(year=2024, dry_run=True, diagnostics=True)
|
|
584
|
-
|
|
584
|
+
sample = helper.dask.load(year=2024, limit=5).compute()
|
|
585
585
|
```
|
|
586
586
|
|
|
587
587
|
### Resilient joins
|
|
@@ -667,7 +667,7 @@ with DataHelper.session(scheduler_address="tcp://scheduler:8786") as client:
|
|
|
667
667
|
by_region = ddf.groupby("region").size().compute()
|
|
668
668
|
|
|
669
669
|
# Preview persisted data before the managed session closes.
|
|
670
|
-
|
|
670
|
+
sample = helper.load(year=2024, limit=5, persist=True).compute()
|
|
671
671
|
```
|
|
672
672
|
|
|
673
673
|
### Shared sessions for repeated notebook cells
|
|
@@ -961,8 +961,7 @@ with SqlDatabaseResource(config) as db:
|
|
|
961
961
|
from boti_data import DataGateway, SqlDatabaseConfig
|
|
962
962
|
|
|
963
963
|
gateway = DataGateway(
|
|
964
|
-
|
|
965
|
-
config=SqlDatabaseConfig(connection_url="sqlite:///example.db", query_only=True),
|
|
964
|
+
SqlDatabaseConfig(connection_url="sqlite:///example.db", query_only=True),
|
|
966
965
|
)
|
|
967
966
|
```
|
|
968
967
|
|
|
@@ -1010,7 +1009,7 @@ config = SqlDatabaseConfig(
|
|
|
1010
1009
|
worker_connection_env_var="DB_URL",
|
|
1011
1010
|
)
|
|
1012
1011
|
|
|
1013
|
-
with DataHelper.session(cluster_factory=LocalCluster, n_workers
|
|
1012
|
+
with DataHelper.session(cluster_factory=LocalCluster, cluster_kwargs={"n_workers": 4}) as client:
|
|
1014
1013
|
with DataHelper(config, table="events") as helper:
|
|
1015
1014
|
ddf = helper.dask.load(event_type="purchase", persist=True)
|
|
1016
1015
|
result = ddf.groupby("user_id").size().compute()
|
|
@@ -65,9 +65,9 @@ class DatacubeRequest(BaseModel):
|
|
|
65
65
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
66
66
|
|
|
67
67
|
filters: dict[str, Any] = Field(default_factory=dict)
|
|
68
|
-
cube: str | None = Field(default=None)
|
|
68
|
+
cube: str | None = Field(default=None, max_length=256)
|
|
69
69
|
limit: int | None = Field(default=None)
|
|
70
|
-
return_type: str | None = Field(default=None)
|
|
70
|
+
return_type: str | None = Field(default=None, max_length=32)
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
class DatacubeConfig(ResourceConfig):
|
|
@@ -123,8 +123,8 @@ class SqlPartitionedLoadRequest(BaseModel):
|
|
|
123
123
|
params: dict[str, Any] = Field(default_factory=dict)
|
|
124
124
|
partitioned: bool = True
|
|
125
125
|
partition_strategy: PartitionStrategy = "offset"
|
|
126
|
-
partition_column: str | None = None
|
|
127
|
-
order_column: str | None = None
|
|
126
|
+
partition_column: str | None = Field(default=None, max_length=256)
|
|
127
|
+
order_column: str | None = Field(default=None, max_length=256)
|
|
128
128
|
chunk_size: int = Field(default=50_000, ge=1)
|
|
129
129
|
single_fetch_threshold: int | None = Field(default=None, ge=0)
|
|
130
130
|
limit: int | None = Field(default=None, ge=1)
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import hashlib
|
|
12
|
+
import logging
|
|
12
13
|
import re
|
|
13
14
|
import sys
|
|
14
15
|
import threading
|
|
@@ -25,6 +26,8 @@ from boti_data.db.sql_model_module_injection import register_as_module_attribute
|
|
|
25
26
|
from boti_data.db.sql_model_registry_types import DefaultBase, ModelBuildContext, RegistryConfig
|
|
26
27
|
from boti_data.db.sqlalchemy_async import ensure_greenlet_available
|
|
27
28
|
|
|
29
|
+
_logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
28
31
|
try:
|
|
29
32
|
# Importing this module has the side effect of registering pgvector's
|
|
30
33
|
# 'vector' type name into SQLAlchemy's Postgres dialect
|
|
@@ -34,8 +37,10 @@ try:
|
|
|
34
37
|
# works the same as before for consumers who never touch pgvector and
|
|
35
38
|
# haven't installed boti-data[pgvector].
|
|
36
39
|
import pgvector.sqlalchemy # noqa: F401
|
|
37
|
-
except ImportError:
|
|
38
|
-
|
|
40
|
+
except ImportError as exc:
|
|
41
|
+
# Debug level only — this is an expected/best-effort probe for the
|
|
42
|
+
# overwhelming majority of consumers who never install the pgvector extra.
|
|
43
|
+
_logger.debug("pgvector.sqlalchemy not available, vector type registration skipped: %s", exc)
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
class SqlModelRegistry:
|
|
@@ -82,7 +82,7 @@ class RowFilter:
|
|
|
82
82
|
async def transform(self, df: pd.DataFrame, **kwargs: Any) -> pd.DataFrame:
|
|
83
83
|
for expr in self._predicates:
|
|
84
84
|
_reject_dunder_expr(expr)
|
|
85
|
-
mask = df.eval(expr)
|
|
85
|
+
mask = df.eval(expr) # nosec CWE-94 -- dunder-chain rejected above; see class docstring's Security section
|
|
86
86
|
df = df.loc[mask].reset_index(drop=True)
|
|
87
87
|
return df
|
|
88
88
|
|
|
@@ -116,7 +116,7 @@ class DerivedColumn:
|
|
|
116
116
|
async def transform(self, df: pd.DataFrame, **kwargs: Any) -> pd.DataFrame:
|
|
117
117
|
for name, expr in self._columns.items():
|
|
118
118
|
_reject_dunder_expr(expr)
|
|
119
|
-
df[name] = df.eval(expr)
|
|
119
|
+
df[name] = df.eval(expr) # nosec CWE-94 -- dunder-chain rejected above; see class docstring's Security section
|
|
120
120
|
return df
|
|
121
121
|
|
|
122
122
|
|
|
@@ -17,7 +17,7 @@ class GatewayLoadRequest(BaseModel):
|
|
|
17
17
|
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
|
18
18
|
|
|
19
19
|
# Source
|
|
20
|
-
sql: str | None = None
|
|
20
|
+
sql: str | None = Field(default=None, max_length=100_000)
|
|
21
21
|
statement: Any = None
|
|
22
22
|
model: Any = None
|
|
23
23
|
filters: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -43,13 +43,13 @@ class GatewayLoadRequest(BaseModel):
|
|
|
43
43
|
# IN-chunking controls
|
|
44
44
|
in_chunk_size: int | None = None
|
|
45
45
|
in_chunk_concurrency: int | None = None
|
|
46
|
-
in_chunk_strategy: str = "auto"
|
|
46
|
+
in_chunk_strategy: str = Field(default="auto", max_length=32)
|
|
47
47
|
|
|
48
48
|
# Partitioned load controls
|
|
49
49
|
partitioned: bool | None = None
|
|
50
|
-
partition_strategy: str | None = None
|
|
51
|
-
partition_column: str | None = None
|
|
52
|
-
order_column: str | None = None
|
|
50
|
+
partition_strategy: str | None = Field(default=None, max_length=32)
|
|
51
|
+
partition_column: str | None = Field(default=None, max_length=256)
|
|
52
|
+
order_column: str | None = Field(default=None, max_length=256)
|
|
53
53
|
chunk_size: int | None = Field(default=None, ge=1)
|
|
54
54
|
single_fetch_threshold: int | None = Field(default=None, ge=0)
|
|
55
55
|
max_concurrent_fetches: int | None = None
|
|
@@ -226,4 +226,4 @@ class PostProcessor:
|
|
|
226
226
|
logger.info(
|
|
227
227
|
f"Gateway load completed elapsed={elapsed:.2f}s metrics={describe_frame(frame)}"
|
|
228
228
|
)
|
|
229
|
-
logger.info(f"Gateway load graph metrics={describe_frame(frame)}")
|
|
229
|
+
logger.info(f"Gateway load graph metrics={describe_frame(frame)}") # nosec CWE-117 -- counts/engine-name only, never data-derived
|
|
@@ -50,8 +50,8 @@ class DataFrameParams(BaseModel):
|
|
|
50
50
|
fieldnames: tuple[str, ...] | None = None
|
|
51
51
|
column_names: list[str] | None = None
|
|
52
52
|
chunk_size: int | None = Field(default=None, ge=1)
|
|
53
|
-
index_col: str | None = None
|
|
54
|
-
datetime_index: str | None = None
|
|
53
|
+
index_col: str | None = Field(default=None, max_length=256)
|
|
54
|
+
datetime_index: str | None = Field(default=None, max_length=256)
|
|
55
55
|
return_type: ReturnType = "dask"
|
|
56
56
|
execution_mode: ExecutionMode = "auto"
|
|
57
57
|
|
|
@@ -133,7 +133,7 @@ class SqlLoadRequest(BaseModel):
|
|
|
133
133
|
|
|
134
134
|
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
|
135
135
|
|
|
136
|
-
sql: str | None = None
|
|
136
|
+
sql: str | None = Field(default=None, max_length=100_000)
|
|
137
137
|
statement: Any | None = None
|
|
138
138
|
model: Any | None = None
|
|
139
139
|
filters: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -52,7 +52,7 @@ async def aload_configured_execute_async_resource(
|
|
|
52
52
|
model, stmt = await ctx.get_configured_select_async(resource, ctx.db_columns)
|
|
53
53
|
t1 = perf_counter()
|
|
54
54
|
if diagnostics:
|
|
55
|
-
resource.logger.info(f"Configured async select reflect_select={t1 - t0:.3f}s")
|
|
55
|
+
resource.logger.info(f"Configured async select reflect_select={t1 - t0:.3f}s") # nosec CWE-117 -- float only
|
|
56
56
|
if ctx.execution_mode == "lazy":
|
|
57
57
|
t2 = perf_counter()
|
|
58
58
|
partitioned_options = build_partitioned_load_options(
|
|
@@ -67,7 +67,7 @@ async def aload_configured_execute_async_resource(
|
|
|
67
67
|
request = build_sql_partitioned_request(options_dict)
|
|
68
68
|
t3 = perf_counter()
|
|
69
69
|
if diagnostics:
|
|
70
|
-
resource.logger.info(f"Build partitioned request elapsed={t3 - t2:.3f}s")
|
|
70
|
+
resource.logger.info(f"Build partitioned request elapsed={t3 - t2:.3f}s") # nosec CWE-117 -- float only
|
|
71
71
|
return await run_async_partitioned_request(ctx, resource, request)
|
|
72
72
|
|
|
73
73
|
df = await read_sql_async(resource, trusted_sql_request(ctx, statement=stmt, model=model))
|
|
@@ -140,6 +140,8 @@ def _left_join(
|
|
|
140
140
|
left_on: Sequence[str] | None = None,
|
|
141
141
|
right_on: Sequence[str] | None = None,
|
|
142
142
|
persist: bool = False,
|
|
143
|
+
resilient: bool = False,
|
|
144
|
+
dry_run: bool = False,
|
|
143
145
|
reset_index: bool = True,
|
|
144
146
|
diagnostics: bool = False,
|
|
145
147
|
logger: Any | None = None,
|
|
@@ -152,6 +154,8 @@ def _left_join(
|
|
|
152
154
|
join_key=join_key,
|
|
153
155
|
join_schema_map=join_schema_map,
|
|
154
156
|
persist=persist,
|
|
157
|
+
resilient=resilient,
|
|
158
|
+
dry_run=dry_run,
|
|
155
159
|
reset_index=reset_index,
|
|
156
160
|
diagnostics=diagnostics,
|
|
157
161
|
logger=logger,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boti-data
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.6
|
|
4
4
|
Summary: Data infrastructure for the Boti ecosystem
|
|
5
5
|
Author: Luis Valverde
|
|
6
6
|
Maintainer-email: Luis Valverde <lvalverdeb@gmail.com>
|
|
@@ -610,13 +610,13 @@ with apply_recommended_dask_config():
|
|
|
610
610
|
|
|
611
611
|
### Dry-run and preview on gateway loads
|
|
612
612
|
|
|
613
|
-
Use `dry_run=True` to build and inspect a lazy Dask load graph without materializing it, and `
|
|
613
|
+
Use `dry_run=True` to build and inspect a lazy Dask load graph without materializing it, and `limit=` to safely sample rows inside the session:
|
|
614
614
|
|
|
615
615
|
```python
|
|
616
616
|
with DataHelper.session(scheduler_address="tcp://scheduler:8786", verify_connectivity=True) as client:
|
|
617
617
|
with DataHelper(config, table="transactions") as helper:
|
|
618
618
|
ddf = helper.dask.load(year=2024, dry_run=True, diagnostics=True)
|
|
619
|
-
|
|
619
|
+
sample = helper.dask.load(year=2024, limit=5).compute()
|
|
620
620
|
```
|
|
621
621
|
|
|
622
622
|
### Resilient joins
|
|
@@ -702,7 +702,7 @@ with DataHelper.session(scheduler_address="tcp://scheduler:8786") as client:
|
|
|
702
702
|
by_region = ddf.groupby("region").size().compute()
|
|
703
703
|
|
|
704
704
|
# Preview persisted data before the managed session closes.
|
|
705
|
-
|
|
705
|
+
sample = helper.load(year=2024, limit=5, persist=True).compute()
|
|
706
706
|
```
|
|
707
707
|
|
|
708
708
|
### Shared sessions for repeated notebook cells
|
|
@@ -996,8 +996,7 @@ with SqlDatabaseResource(config) as db:
|
|
|
996
996
|
from boti_data import DataGateway, SqlDatabaseConfig
|
|
997
997
|
|
|
998
998
|
gateway = DataGateway(
|
|
999
|
-
|
|
1000
|
-
config=SqlDatabaseConfig(connection_url="sqlite:///example.db", query_only=True),
|
|
999
|
+
SqlDatabaseConfig(connection_url="sqlite:///example.db", query_only=True),
|
|
1001
1000
|
)
|
|
1002
1001
|
```
|
|
1003
1002
|
|
|
@@ -1045,7 +1044,7 @@ config = SqlDatabaseConfig(
|
|
|
1045
1044
|
worker_connection_env_var="DB_URL",
|
|
1046
1045
|
)
|
|
1047
1046
|
|
|
1048
|
-
with DataHelper.session(cluster_factory=LocalCluster, n_workers
|
|
1047
|
+
with DataHelper.session(cluster_factory=LocalCluster, cluster_kwargs={"n_workers": 4}) as client:
|
|
1049
1048
|
with DataHelper(config, table="events") as helper:
|
|
1050
1049
|
ddf = helper.dask.load(event_type="purchase", persist=True)
|
|
1051
1050
|
result = ddf.groupby("user_id").size().compute()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|