boti-data 1.3.3__tar.gz → 1.3.5__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.
Files changed (120) hide show
  1. {boti_data-1.3.3 → boti_data-1.3.5}/PKG-INFO +1 -1
  2. {boti_data-1.3.3 → boti_data-1.3.5}/pyproject.toml +1 -1
  3. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/datacube/contract.py +2 -2
  4. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/partitioned_types.py +2 -2
  5. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_model_registry.py +7 -2
  6. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/enrichment/transformers.py +2 -2
  7. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/load_request.py +5 -5
  8. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/post_process.py +1 -1
  9. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/requests.py +3 -3
  10. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/sql_strategy_configured_async.py +2 -2
  11. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/pipelines/sinks_common.py +26 -2
  12. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data.egg-info/PKG-INFO +1 -1
  13. {boti_data-1.3.3 → boti_data-1.3.5}/README.md +0 -0
  14. {boti_data-1.3.3 → boti_data-1.3.5}/setup.cfg +0 -0
  15. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/__init__.py +0 -0
  16. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/arrow_schema.py +0 -0
  17. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/connection_catalog.py +0 -0
  18. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/datacube/__init__.py +0 -0
  19. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/datacube/artifact.py +0 -0
  20. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/datacube/base.py +0 -0
  21. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/datacube/resource.py +0 -0
  22. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/dataset/__init__.py +0 -0
  23. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/dataset/hybrid.py +0 -0
  24. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/dataset/hybrid_support.py +0 -0
  25. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/__init__.py +0 -0
  26. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/arrow_schema_mapper.py +0 -0
  27. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/engine_registry.py +0 -0
  28. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/partitioned_execution.py +0 -0
  29. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/partitioned_loader.py +0 -0
  30. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/partitioned_planner.py +0 -0
  31. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/partitioned_planner_async.py +0 -0
  32. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/partitioned_planning_math.py +0 -0
  33. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/partitioned_row_materialization.py +0 -0
  34. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/partitioned_statements.py +0 -0
  35. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_config.py +0 -0
  36. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_engine.py +0 -0
  37. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_manager.py +0 -0
  38. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_model_builder.py +0 -0
  39. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_model_module_injection.py +0 -0
  40. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_model_registry_types.py +0 -0
  41. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_readonly.py +0 -0
  42. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_repository.py +0 -0
  43. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sql_resource.py +0 -0
  44. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/sqlalchemy_async.py +0 -0
  45. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/db/vector_search.py +0 -0
  46. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/enrichment/__init__.py +0 -0
  47. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/enrichment/async_enricher.py +0 -0
  48. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/enrichment/composite.py +0 -0
  49. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/enrichment/protocol.py +0 -0
  50. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/enrichment/specs.py +0 -0
  51. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/field_map.py +0 -0
  52. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/__init__.py +0 -0
  53. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/arrow_kernels.py +0 -0
  54. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/arrow_kernels_comparison.py +0 -0
  55. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/arrow_kernels_compile.py +0 -0
  56. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/arrow_kernels_string.py +0 -0
  57. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/backend_ops.py +0 -0
  58. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/expressions.py +0 -0
  59. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/handler.py +0 -0
  60. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/parquet_compiler.py +0 -0
  61. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/utils.py +0 -0
  62. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/filters/value_parsing.py +0 -0
  63. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/__init__.py +0 -0
  64. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/_backend_strategies.py +0 -0
  65. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/_factory.py +0 -0
  66. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/_gateway_init.py +0 -0
  67. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/_payloads.py +0 -0
  68. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/_series_filters.py +0 -0
  69. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/arrow_adapters.py +0 -0
  70. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/build_context.py +0 -0
  71. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/chunking.py +0 -0
  72. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/configured_load.py +0 -0
  73. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/core.py +0 -0
  74. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/core_describe.py +0 -0
  75. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/core_load.py +0 -0
  76. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/datacube_strategy.py +0 -0
  77. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/frame_convert.py +0 -0
  78. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/frame_strategies.py +0 -0
  79. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/incremental.py +0 -0
  80. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/load_execution.py +0 -0
  81. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/loaders.py +0 -0
  82. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/normalization.py +0 -0
  83. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/parquet_strategy.py +0 -0
  84. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/planning.py +0 -0
  85. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/policies.py +0 -0
  86. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/return_type.py +0 -0
  87. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/select_cache.py +0 -0
  88. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/semi_join.py +0 -0
  89. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/sql_describe.py +0 -0
  90. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/sql_guard.py +0 -0
  91. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/sql_partitioned_exec.py +0 -0
  92. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/sql_size_estimation.py +0 -0
  93. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/gateway/sql_strategy.py +0 -0
  94. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/helper.py +0 -0
  95. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/joins.py +0 -0
  96. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/__init__.py +0 -0
  97. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/config.py +0 -0
  98. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/discovery.py +0 -0
  99. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/file_info.py +0 -0
  100. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/filesystem.py +0 -0
  101. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/reader.py +0 -0
  102. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/reader_config.py +0 -0
  103. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/resource.py +0 -0
  104. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/parquet/schema_filters.py +0 -0
  105. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/pipelines/__init__.py +0 -0
  106. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/pipelines/base.py +0 -0
  107. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/pipelines/parquet_pipeline.py +0 -0
  108. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/pipelines/registry.py +0 -0
  109. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/pipelines/sinks.py +0 -0
  110. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/pipelines/sinks_parquet.py +0 -0
  111. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/pipelines/sinks_text.py +0 -0
  112. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/py.typed +0 -0
  113. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/schema.py +0 -0
  114. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/watermark/__init__.py +0 -0
  115. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/watermark/incremental.py +0 -0
  116. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data/watermark/store.py +0 -0
  117. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data.egg-info/SOURCES.txt +0 -0
  118. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data.egg-info/dependency_links.txt +0 -0
  119. {boti_data-1.3.3 → boti_data-1.3.5}/src/boti_data.egg-info/requires.txt +0 -0
  120. {boti_data-1.3.3 → boti_data-1.3.5}/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
3
+ Version: 1.3.5
4
4
  Summary: Data infrastructure for the Boti ecosystem
5
5
  Author: Luis Valverde
6
6
  Maintainer-email: Luis Valverde <lvalverdeb@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "boti-data"
3
- version = "1.3.3"
3
+ version = "1.3.5"
4
4
  description = "Data infrastructure for the Boti ecosystem"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -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
- pass
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))
@@ -146,6 +146,30 @@ def _rm_recursive(fs: fsspec.AbstractFileSystem, path: str) -> None:
146
146
  fs.rm(path)
147
147
 
148
148
 
149
+ def _rebase_staged_path(
150
+ fs: fsspec.AbstractFileSystem,
151
+ path: str,
152
+ staging_path: str,
153
+ target_root: str,
154
+ ) -> str:
155
+ """Rewrite *path* from the staging tree to the target tree.
156
+
157
+ ``glob()``/``find()`` on most fsspec backends (S3, memory, ...) return
158
+ protocol-stripped paths (``bucket/key``) even when queried with a
159
+ scheme-prefixed directory (``s3://bucket/key``) -- but *staging_path*/
160
+ *target_root* here are whatever the caller originally passed in, which
161
+ for a ``Datasources``-resolved destination is scheme-prefixed. A naive
162
+ ``path.replace(staging_path, target_root)`` then silently no-ops (no
163
+ substring match, string returned unchanged) instead of raising, so both
164
+ sides are normalized through ``_strip_protocol()`` first to guarantee the
165
+ replace actually matches regardless of which form *path* arrived in.
166
+ """
167
+ normalized_path = fs._strip_protocol(path)
168
+ normalized_staging = fs._strip_protocol(staging_path)
169
+ normalized_target = fs._strip_protocol(target_root)
170
+ return normalized_path.replace(normalized_staging, normalized_target, 1)
171
+
172
+
149
173
  def _move_staged_files(
150
174
  fs: fsspec.AbstractFileSystem,
151
175
  staged_files: Sequence[str],
@@ -165,7 +189,7 @@ def _move_staged_files(
165
189
  so the phantom entry never gets a chance to appear.
166
190
  """
167
191
  for staged_file in staged_files:
168
- destination = staged_file.replace(staging_path, target_root, 1)
192
+ destination = _rebase_staged_path(fs, staged_file, staging_path, target_root)
169
193
  parent = destination.rsplit("/", 1)[0]
170
194
  if parent and parent != destination:
171
195
  fs.makedirs(parent, exist_ok=True)
@@ -212,7 +236,7 @@ def _write_with_staging(
212
236
  f"The newly written data is intact at {staging_path!r}."
213
237
  ) from exc
214
238
 
215
- return [file.replace(staging_path, target_root, 1) for file in files]
239
+ return [_rebase_staged_path(fs, file, staging_path, target_root) for file in files]
216
240
 
217
241
 
218
242
  @dataclass(slots=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boti-data
3
- Version: 1.3.3
3
+ Version: 1.3.5
4
4
  Summary: Data infrastructure for the Boti ecosystem
5
5
  Author: Luis Valverde
6
6
  Maintainer-email: Luis Valverde <lvalverdeb@gmail.com>
File without changes
File without changes