gispulse 2.2.0__tar.gz → 2.2.1__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 (335) hide show
  1. {gispulse-2.2.0/src/gispulse.egg-info → gispulse-2.2.1}/PKG-INFO +1 -1
  2. {gispulse-2.2.0 → gispulse-2.2.1}/pyproject.toml +1 -1
  3. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/datamart.py +61 -14
  4. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/loader.py +84 -4
  5. {gispulse-2.2.0 → gispulse-2.2.1/src/gispulse.egg-info}/PKG-INFO +1 -1
  6. {gispulse-2.2.0 → gispulse-2.2.1}/LICENSE +0 -0
  7. {gispulse-2.2.0 → gispulse-2.2.1}/LICENSE-COMMERCIAL.md +0 -0
  8. {gispulse-2.2.0 → gispulse-2.2.1}/README.md +0 -0
  9. {gispulse-2.2.0 → gispulse-2.2.1}/setup.cfg +0 -0
  10. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/__init__.py +0 -0
  11. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/_compat.py +0 -0
  12. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/_pyogrio_warnings.py +0 -0
  13. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/__init__.py +0 -0
  14. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/apicarto.py +0 -0
  15. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/__init__.py +0 -0
  16. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/action_dispatcher.py +0 -0
  17. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/bus_message.py +0 -0
  18. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/circuit_breaker.py +0 -0
  19. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/dlq.py +0 -0
  20. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/enums.py +0 -0
  21. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/event_router.py +0 -0
  22. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/pg_notify.py +0 -0
  23. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/pool.py +0 -0
  24. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/predicate_evaluator.py +0 -0
  25. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/state_store.py +0 -0
  26. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/trigger_manager.py +0 -0
  27. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/workers/__init__.py +0 -0
  28. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/workers/base_worker.py +0 -0
  29. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/workers/dispatch_worker.py +0 -0
  30. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/esb/workers/identify_worker.py +0 -0
  31. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/__init__.py +0 -0
  32. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/app.py +0 -0
  33. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/auth.py +0 -0
  34. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/dataset_ops.py +0 -0
  35. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/dependencies.py +0 -0
  36. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/error_handlers.py +0 -0
  37. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/event_hub.py +0 -0
  38. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/layer_utils.py +0 -0
  39. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/middleware/__init__.py +0 -0
  40. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/middleware/audit_middleware.py +0 -0
  41. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/middleware/metrics_middleware.py +0 -0
  42. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/middleware/read_only.py +0 -0
  43. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/portal_app.py +0 -0
  44. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/rate_limit.py +0 -0
  45. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/__init__.py +0 -0
  46. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/_upload_utils.py +0 -0
  47. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/auth_router.py +0 -0
  48. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/capabilities_router.py +0 -0
  49. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/catalog_router.py +0 -0
  50. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/datasets_router.py +0 -0
  51. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/esb_router.py +0 -0
  52. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/examples_router.py +0 -0
  53. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/filter_router.py +0 -0
  54. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/jobs_router.py +0 -0
  55. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/marketplace_router.py +0 -0
  56. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/ogc_features_router.py +0 -0
  57. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/pipelines_router.py +0 -0
  58. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/portal_datasets_router.py +0 -0
  59. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/portal_features_router.py +0 -0
  60. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/portal_router.py +0 -0
  61. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/portal_sql_router.py +0 -0
  62. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/portal_upload_router.py +0 -0
  63. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/projects_router.py +0 -0
  64. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/relations_router.py +0 -0
  65. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/rules_router.py +0 -0
  66. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/scenarios_router.py +0 -0
  67. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/schedules_router.py +0 -0
  68. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/sessions_router.py +0 -0
  69. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/system_router.py +0 -0
  70. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/templates_router.py +0 -0
  71. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/tiles_router.py +0 -0
  72. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/triggers_router.py +0 -0
  73. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/viewer_router.py +0 -0
  74. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/watchers_router.py +0 -0
  75. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/routers/ws_router.py +0 -0
  76. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/schemas.py +0 -0
  77. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/http/serve_app.py +0 -0
  78. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/mcp/__init__.py +0 -0
  79. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/mcp/dryrun.py +0 -0
  80. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/mcp/server.py +0 -0
  81. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/mcp/workdir.py +0 -0
  82. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/metrics.py +0 -0
  83. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/ogc/__init__.py +0 -0
  84. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/ogc/auth.py +0 -0
  85. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/ogc/loader.py +0 -0
  86. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/ogc/wfs_client.py +0 -0
  87. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/ogc/wfs_fetcher.py +0 -0
  88. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/rest/__init__.py +0 -0
  89. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/rest/rest_fetcher.py +0 -0
  90. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/rest/rest_table_fetcher.py +0 -0
  91. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/stac/__init__.py +0 -0
  92. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/stac/stac_fetcher.py +0 -0
  93. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/webhooks/__init__.py +0 -0
  94. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/adapters/webhooks/http_client.py +0 -0
  95. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/app.py +0 -0
  96. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/__init__.py +0 -0
  97. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/_attribute_sql.py +0 -0
  98. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/_geometry_sql.py +0 -0
  99. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/base.py +0 -0
  100. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/classification.py +0 -0
  101. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/clustering.py +0 -0
  102. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/density.py +0 -0
  103. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/network.py +0 -0
  104. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/network_components.py +0 -0
  105. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/network_graph.py +0 -0
  106. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/network_topology.py +0 -0
  107. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/overlay.py +0 -0
  108. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/palettes.py +0 -0
  109. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/pointcloud.py +0 -0
  110. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/polygon_topology.py +0 -0
  111. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/postgis_sql.py +0 -0
  112. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/raster.py +0 -0
  113. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/registry.py +0 -0
  114. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/relation_detector.py +0 -0
  115. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/schema.py +0 -0
  116. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/selection.py +0 -0
  117. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/spatial_stats.py +0 -0
  118. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/sql_pushdown.py +0 -0
  119. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/strategy.py +0 -0
  120. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/temporal.py +0 -0
  121. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/transforms.py +0 -0
  122. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/validation.py +0 -0
  123. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/__init__.py +0 -0
  124. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/aggregate.py +0 -0
  125. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/assign_projection.py +0 -0
  126. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/boundary.py +0 -0
  127. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/buffer.py +0 -0
  128. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/calculate.py +0 -0
  129. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/centroid_area.py +0 -0
  130. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/chaikin.py +0 -0
  131. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/classify.py +0 -0
  132. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/clip.py +0 -0
  133. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/concave_hull.py +0 -0
  134. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/diff.py +0 -0
  135. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/dissolve.py +0 -0
  136. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/extract_holes.py +0 -0
  137. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/extract_ops.py +0 -0
  138. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/filter.py +0 -0
  139. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/force_geometry_type.py +0 -0
  140. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/intersects.py +0 -0
  141. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/line_merge.py +0 -0
  142. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/line_ops.py +0 -0
  143. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/merge.py +0 -0
  144. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/nearest.py +0 -0
  145. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/offset_curve.py +0 -0
  146. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/parts.py +0 -0
  147. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/polygonize.py +0 -0
  148. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/reproject.py +0 -0
  149. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/shape_ops_advanced.py +0 -0
  150. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/shape_ops_basic.py +0 -0
  151. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/simplify.py +0 -0
  152. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/snap_grid.py +0 -0
  153. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/snap_points.py +0 -0
  154. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/spatial_join.py +0 -0
  155. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/split_lines.py +0 -0
  156. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/union.py +0 -0
  157. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/capabilities/vector/voronoi.py +0 -0
  158. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/__init__.py +0 -0
  159. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/data/basemaps.json +0 -0
  160. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/data/epsg_common.json +0 -0
  161. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/models.py +0 -0
  162. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/__init__.py +0 -0
  163. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/base.py +0 -0
  164. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/basemaps.py +0 -0
  165. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/flux_ign.py +0 -0
  166. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/flux_osm.py +0 -0
  167. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/opendata_datagouv.py +0 -0
  168. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/opendata_hub.py +0 -0
  169. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/opendata_ign.py +0 -0
  170. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/projections.py +0 -0
  171. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/providers/stac_client.py +0 -0
  172. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/registry.py +0 -0
  173. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/catalog/source_bridge.py +0 -0
  174. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/cli.py +0 -0
  175. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/cli_mcp.py +0 -0
  176. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/cli_portal.py +0 -0
  177. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/cli_track.py +0 -0
  178. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/cli_triggers.py +0 -0
  179. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/cli_triggers_watch.py +0 -0
  180. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/cli_watch.py +0 -0
  181. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/config.py +0 -0
  182. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/__init__.py +0 -0
  183. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/assertions.py +0 -0
  184. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/bulk_ingest.py +0 -0
  185. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/bulk_runner.py +0 -0
  186. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/cache.py +0 -0
  187. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/capability_params.py +0 -0
  188. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/conditions.py +0 -0
  189. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/config.py +0 -0
  190. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/crs.py +0 -0
  191. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/dag.py +0 -0
  192. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/data/worldwide_catalog.yml +0 -0
  193. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/data_pack_signature.py +0 -0
  194. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/dispatcher.py +0 -0
  195. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/enums.py +0 -0
  196. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/explain.py +0 -0
  197. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/fetchers/__init__.py +0 -0
  198. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/fetchers/base.py +0 -0
  199. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/fetchers/geoparquet_s3.py +0 -0
  200. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/fetchers/http_file.py +0 -0
  201. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/fetchers/ogc_client.py +0 -0
  202. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/fetchers/ogc_features.py +0 -0
  203. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/fetchers/stac.py +0 -0
  204. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/fetchers/table_file.py +0 -0
  205. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/filter/__init__.py +0 -0
  206. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/filter/cache.py +0 -0
  207. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/filter/chain.py +0 -0
  208. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/filter/expression.py +0 -0
  209. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/filter/expression_converter.py +0 -0
  210. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/filter/result.py +0 -0
  211. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/filter/service.py +0 -0
  212. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/filter/types.py +0 -0
  213. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/graph.py +0 -0
  214. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/io/__init__.py +0 -0
  215. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/io/geoparquet.py +0 -0
  216. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/licence_format.py +0 -0
  217. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/logging.py +0 -0
  218. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/manifest_v3.py +0 -0
  219. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/models.py +0 -0
  220. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/network_graph_handle.py +0 -0
  221. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/observability.py +0 -0
  222. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/pipeline.py +0 -0
  223. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/pipeline_schema.py +0 -0
  224. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/plugin_contracts.py +0 -0
  225. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/plugin_hub.py +0 -0
  226. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/plugin_model.py +0 -0
  227. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/predicates.py +0 -0
  228. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/pricing_catalog.yml +0 -0
  229. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/registry.py +0 -0
  230. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/regulatory_zoning_entry.py +0 -0
  231. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/relations.py +0 -0
  232. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/session.py +0 -0
  233. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/sources.py +0 -0
  234. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/spatial_index.py +0 -0
  235. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/sql_safety.py +0 -0
  236. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/ssrf.py +0 -0
  237. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/core/zoning_normalizer.py +0 -0
  238. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/diagnostics/__init__.py +0 -0
  239. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/diagnostics/system.py +0 -0
  240. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/dsl/__init__.py +0 -0
  241. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/dsl/expression_parser.py +0 -0
  242. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/dsl/geom_fcts.py +0 -0
  243. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/__init__.py +0 -0
  244. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/capability_executor.py +0 -0
  245. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/graph_executor.py +0 -0
  246. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/job_queue.py +0 -0
  247. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/job_queue_factory.py +0 -0
  248. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/metering.py +0 -0
  249. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/pipeline_executor.py +0 -0
  250. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/runner.py +0 -0
  251. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/scenario_runner.py +0 -0
  252. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/scheduler.py +0 -0
  253. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/session_manager.py +0 -0
  254. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/trigger_bridge.py +0 -0
  255. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/orchestration/worker.py +0 -0
  256. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/__init__.py +0 -0
  257. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/audit.py +0 -0
  258. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/auth_models.py +0 -0
  259. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/auth_repository.py +0 -0
  260. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/bridge.py +0 -0
  261. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/change_log_watcher.py +0 -0
  262. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/changelog_doctor.py +0 -0
  263. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/changelog_reader.py +0 -0
  264. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/duckdb_change_detector.py +0 -0
  265. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/duckdb_diff_engine.py +0 -0
  266. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/duckdb_engine.py +0 -0
  267. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/duckdb_engine_adapter.py +0 -0
  268. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/duckdb_relations.py +0 -0
  269. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/engine.py +0 -0
  270. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/engine_factory.py +0 -0
  271. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/file_blob_cdc.py +0 -0
  272. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/geonode.py +0 -0
  273. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/gpkg.py +0 -0
  274. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/gpkg_connection.py +0 -0
  275. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/gpkg_engine.py +0 -0
  276. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/gpkg_repository.py +0 -0
  277. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/gpkg_schema.py +0 -0
  278. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/gpkg_spatial.py +0 -0
  279. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/io.py +0 -0
  280. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/licence.py +0 -0
  281. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/postgis.py +0 -0
  282. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/project_io.py +0 -0
  283. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/raster_io.py +0 -0
  284. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/repository.py +0 -0
  285. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/schedule_repository.py +0 -0
  286. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/schema.py +0 -0
  287. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/session_provisioner.py +0 -0
  288. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/sld_converter.py +0 -0
  289. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/source_watcher.py +0 -0
  290. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/spatial_queries.py +0 -0
  291. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/spatialite_engine.py +0 -0
  292. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/spatialite_session.py +0 -0
  293. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/sql_dialect.py +0 -0
  294. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/sql_guardrails.py +0 -0
  295. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/sqlite_repository.py +0 -0
  296. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/storage.py +0 -0
  297. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/style_converter.py +0 -0
  298. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/style_sidecar.py +0 -0
  299. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/tier.py +0 -0
  300. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/virtual_dataset.py +0 -0
  301. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/persistence/watcher_registry.py +0 -0
  302. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/plugins/__init__.py +0 -0
  303. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/plugins/api.py +0 -0
  304. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/plugins/datagouv_refresh.py +0 -0
  305. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/plugins/mcp_pilot.py +0 -0
  306. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/plugins/pipeline.py +0 -0
  307. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/plugins/sources.py +0 -0
  308. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/plugins/spatial.py +0 -0
  309. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/plugins/worldwide_source.py +0 -0
  310. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/rules/__init__.py +0 -0
  311. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/rules/engine.py +0 -0
  312. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/rules/loader.py +0 -0
  313. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/rules/operation_executor.py +0 -0
  314. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/rules/predicates.py +0 -0
  315. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/rules/trigger_evaluator.py +0 -0
  316. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/rules/validation.py +0 -0
  317. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/__init__.py +0 -0
  318. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/config_loader.py +0 -0
  319. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/dialect_scanner.py +0 -0
  320. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/duckdb_engine.py +0 -0
  321. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/engine_inference.py +0 -0
  322. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/headless_runtime.py +0 -0
  323. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/layer_registry.py +0 -0
  324. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/manifest_runner.py +0 -0
  325. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/predicate_dsl.py +0 -0
  326. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/source_watch.py +0 -0
  327. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/sqlite_retry.py +0 -0
  328. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/runtime/validation_runner.py +0 -0
  329. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse/telemetry.py +0 -0
  330. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse.egg-info/SOURCES.txt +0 -0
  331. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse.egg-info/dependency_links.txt +0 -0
  332. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse.egg-info/entry_points.txt +0 -0
  333. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse.egg-info/requires.txt +0 -0
  334. {gispulse-2.2.0 → gispulse-2.2.1}/src/gispulse.egg-info/top_level.txt +0 -0
  335. {gispulse-2.2.0 → gispulse-2.2.1}/tests/test_read_only_middleware.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gispulse
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Modular geospatial engine with business rules, triggers, and dual operating modes (DuckDB/PostGIS)
5
5
  Author-email: ImagoData <contact@imagodata.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gispulse"
7
- version = "2.2.0"
7
+ version = "2.2.1"
8
8
  description = "Modular geospatial engine with business rules, triggers, and dual operating modes (DuckDB/PostGIS)"
9
9
  license = "AGPL-3.0-or-later"
10
10
  requires-python = ">=3.10"
@@ -19,8 +19,14 @@ from the ``GISPULSE_DATAMARTS`` environment variable (a JSON object), e.g.::
19
19
 
20
20
  GISPULSE_DATAMARTS='{
21
21
  "referentiel": {"location": "s3://bucket/marts/referentiel"},
22
- "local": {"location": "/data/marts/local", "table_pattern": "{table}.parquet"}
22
+ "local": {"location": "/data/marts/local", "table_pattern": "{table}.parquet"},
23
+ "warehouse": {"location": "/data/marts/warehouse.duckdb", "kind": "duckdb"}
23
24
  }'
25
+
26
+ A ``kind: "duckdb"`` mart points :attr:`Datamart.location` at a single
27
+ DuckDB database file; ``datamart://warehouse/<table>`` then reads the table
28
+ of that name from inside the file (attached read-only, bbox push-down still
29
+ applies via an ``ST_Intersects`` predicate on a ``geom`` column).
24
30
  """
25
31
 
26
32
  from __future__ import annotations
@@ -42,17 +48,33 @@ DATAMARTS_ENV = "GISPULSE_DATAMARTS"
42
48
  DATAMART_SCHEME = "datamart"
43
49
 
44
50
 
51
+ #: Supported datamart backends.
52
+ DATAMART_KINDS = ("parquet", "duckdb")
53
+
54
+
45
55
  @dataclass(frozen=True)
46
56
  class Datamart:
47
57
  """Declaration of one curated table store.
48
58
 
59
+ Two backends are supported, selected by :attr:`kind`:
60
+
61
+ * ``"parquet"`` (default) — *location* is a directory / ``s3://`` /
62
+ ``https://`` prefix holding **one file per table**; the file is
63
+ addressed by joining *location* with *table_pattern* and read as an
64
+ ordinary source (DuckDB-scannable, bbox push-down applies).
65
+ * ``"duckdb"`` — *location* is the path to a **single DuckDB database
66
+ file** (``.duckdb``); a table is a relation *inside* that file, read by
67
+ attaching the database read-only and selecting the table.
68
+
49
69
  Args:
50
70
  name: Logical mart name used in ``datamart://<name>/…``.
51
- location: Base location of the tables a directory path or
52
- an ``s3://`` / ``https://`` prefix. Joined with
53
- ``table_pattern`` to address a single table.
54
- table_pattern: Filename pattern for a table, ``{table}`` being the
55
- table name. Defaults to ``"{table}.parquet"``.
71
+ location: Base location: a directory / ``s3://`` / ``https://``
72
+ prefix (``parquet``), or the path to a ``.duckdb``
73
+ file (``duckdb``).
74
+ table_pattern: Filename pattern for a ``parquet`` table, ``{table}``
75
+ being the table name. Defaults to ``"{table}.parquet"``.
76
+ Ignored for ``duckdb`` marts.
77
+ kind: Backend, ``"parquet"`` (default) or ``"duckdb"``.
56
78
  crs: Optional CRS to force on tables that declare none.
57
79
  metadata: Free-form descriptive metadata.
58
80
  """
@@ -60,13 +82,31 @@ class Datamart:
60
82
  name: str
61
83
  location: str
62
84
  table_pattern: str = "{table}.parquet"
85
+ kind: str = "parquet"
63
86
  crs: str | None = None
64
87
  metadata: dict[str, Any] = field(default_factory=dict)
65
88
 
89
+ def __post_init__(self) -> None:
90
+ if self.kind not in DATAMART_KINDS:
91
+ raise ValueError(
92
+ f"datamart '{self.name}': unknown kind {self.kind!r} "
93
+ f"(expected one of {DATAMART_KINDS})"
94
+ )
95
+
66
96
  def uri_for(self, table: str) -> str:
67
- """Return the concrete source URI for *table* in this mart."""
97
+ """Return the concrete source URI for *table* (``parquet`` marts).
98
+
99
+ Raises:
100
+ ValueError: for a ``duckdb`` mart — a table there is not a file
101
+ URI; resolve it via the loader's DuckDB-table path instead.
102
+ """
68
103
  if not table:
69
104
  raise ValueError(f"datamart '{self.name}': empty table name")
105
+ if self.kind != "parquet":
106
+ raise ValueError(
107
+ f"datamart '{self.name}': uri_for is only valid for parquet "
108
+ f"marts, not kind={self.kind!r}"
109
+ )
70
110
  filename = self.table_pattern.format(table=table)
71
111
  base = self.location.rstrip("/")
72
112
  return f"{base}/{filename}"
@@ -141,13 +181,19 @@ class DatamartRegistry:
141
181
  if not isinstance(spec, dict) or "location" not in spec:
142
182
  log.warning("datamart_env_bad_decl", name=name)
143
183
  continue
144
- self._marts[name] = Datamart(
145
- name=name,
146
- location=str(spec["location"]),
147
- table_pattern=str(spec.get("table_pattern", "{table}.parquet")),
148
- crs=spec.get("crs"),
149
- metadata=dict(spec.get("metadata") or {}),
150
- )
184
+ try:
185
+ self._marts[name] = Datamart(
186
+ name=name,
187
+ location=str(spec["location"]),
188
+ table_pattern=str(
189
+ spec.get("table_pattern", "{table}.parquet")
190
+ ),
191
+ kind=str(spec.get("kind", "parquet")),
192
+ crs=spec.get("crs"),
193
+ metadata=dict(spec.get("metadata") or {}),
194
+ )
195
+ except ValueError as exc:
196
+ log.warning("datamart_env_bad_decl", name=name, error=str(exc))
151
197
 
152
198
 
153
199
  def parse_datamart_uri(uri: str) -> tuple[str, str]:
@@ -177,6 +223,7 @@ DATAMARTS = DatamartRegistry()
177
223
  __all__ = [
178
224
  "DATAMARTS",
179
225
  "DATAMARTS_ENV",
226
+ "DATAMART_KINDS",
180
227
  "DATAMART_SCHEME",
181
228
  "Datamart",
182
229
  "DatamartRegistry",
@@ -119,12 +119,30 @@ class LazyDataset:
119
119
  return _scan_to_gdf(self.scan_sql, crs=self.crs, bbox=bbox)
120
120
 
121
121
 
122
+ @dataclass(frozen=True)
123
+ class _DuckDBTableRef:
124
+ """Reference to a table living inside a single DuckDB database file.
125
+
126
+ Produced by :func:`resolve_source` for a ``duckdb``-kind datamart
127
+ (``datamart://<mart>/<table>`` where the mart's location is a
128
+ ``.duckdb`` file). Unlike a Parquet table it is not a file URI the
129
+ loader can hand to ``read_vector`` / a remote-table scan, so the loader
130
+ reads it via :func:`_duckdb_table_to_gdf` (attach read-only, select).
131
+ """
132
+
133
+ db_path: str
134
+ table: str
135
+ crs: str | None = None
136
+
137
+
122
138
  # ---------------------------------------------------------------------------
123
139
  # Source resolution
124
140
  # ---------------------------------------------------------------------------
125
141
 
126
142
 
127
- def resolve_source(source: "str | Path | AccessSpec | dict[str, Any]") -> "Path | AccessSpec":
143
+ def resolve_source(
144
+ source: "str | Path | AccessSpec | dict[str, Any]",
145
+ ) -> "Path | AccessSpec | _DuckDBTableRef":
128
146
  """Resolve a user-supplied source into a local path or an access spec.
129
147
 
130
148
  Accepted forms:
@@ -162,12 +180,19 @@ def resolve_source(source: "str | Path | AccessSpec | dict[str, Any]") -> "Path
162
180
  scheme = head.lower()
163
181
 
164
182
  # Datamart indirection: resolve datamart://<mart>/<table> to its
165
- # concrete source URI, then resolve that recursively.
183
+ # concrete source. A "parquet" mart yields a file URI resolved
184
+ # recursively; a "duckdb" mart yields a table reference inside a
185
+ # database file, read via _duckdb_table_to_gdf.
166
186
  if scheme == DATAMART_SCHEME:
167
187
  from gispulse.persistence.datamart import DATAMARTS, parse_datamart_uri
168
188
 
169
- mart, table = parse_datamart_uri(raw)
170
- return resolve_source(DATAMARTS.resolve(mart, table))
189
+ mart_name, table = parse_datamart_uri(raw)
190
+ mart = DATAMARTS.get(mart_name)
191
+ if mart.kind == "duckdb":
192
+ if not table:
193
+ raise ValueError(f"datamart '{mart_name}': empty table name")
194
+ return _DuckDBTableRef(db_path=mart.location, table=table, crs=mart.crs)
195
+ return resolve_source(mart.uri_for(table))
171
196
 
172
197
  # GeoNode: geonode://<instance>/<dataset> reads via the instance's
173
198
  # GeoServer WFS endpoint (reuses the OGC fetcher).
@@ -330,6 +355,52 @@ def _scan_to_gdf(
330
355
  return gdf
331
356
 
332
357
 
358
+ def _duckdb_table_to_gdf(
359
+ db_path: str, table: str, *, crs: str | None, bbox: BBox | None
360
+ ) -> gpd.GeoDataFrame:
361
+ """Read a table from a DuckDB database file into a GeoDataFrame.
362
+
363
+ Attaches *db_path* **read-only** (so concurrent readers don't contend on
364
+ a write lock) onto an ephemeral in-memory session, then selects *table*.
365
+ When *bbox* is given an ``ST_Intersects`` envelope predicate is pushed
366
+ into the query against a ``geom`` column (matching the convention of
367
+ :func:`_scan_to_gdf`).
368
+ """
369
+ from gispulse.persistence.duckdb_engine import DuckDBSession
370
+
371
+ # ATTACH cannot be wrapped in the SELECT sub-query DuckDBSession.sql
372
+ # builds, so run it separately on the connection, then SELECT the table
373
+ # — that SELECT *is* wrappable, so geometry/CRS decoding still works.
374
+ db_lit = db_path.replace("'", "''")
375
+ rel = f"_mart.{_quote_ident(table)}"
376
+ query = f"SELECT * FROM {rel}"
377
+ if bbox is not None:
378
+ minx, miny, maxx, maxy = bbox
379
+ query = (
380
+ f"SELECT * FROM {rel} "
381
+ f"WHERE ST_Intersects(geom, ST_MakeEnvelope("
382
+ f"{minx}, {miny}, {maxx}, {maxy}))"
383
+ )
384
+
385
+ session = DuckDBSession()
386
+ session.open()
387
+ try:
388
+ session.conn.execute(f"ATTACH '{db_lit}' AS _mart (READ_ONLY)")
389
+ gdf = session.sql(query)
390
+ finally:
391
+ session.close()
392
+
393
+ if crs and gdf.crs is None:
394
+ gdf = gdf.set_crs(crs)
395
+ return gdf
396
+
397
+
398
+ def _quote_ident(identifier: str) -> str:
399
+ """Quote a SQL identifier (double-quote, doubling embedded quotes)."""
400
+ escaped = identifier.replace('"', '""')
401
+ return f'"{escaped}"'
402
+
403
+
333
404
  # ---------------------------------------------------------------------------
334
405
  # Public entry point
335
406
  # ---------------------------------------------------------------------------
@@ -380,6 +451,15 @@ def load(
380
451
 
381
452
  resolved = resolve_source(source)
382
453
 
454
+ # --- DuckDB-file datamart table ---------------------------------------
455
+ if isinstance(resolved, _DuckDBTableRef):
456
+ if lazy:
457
+ log.debug("loader_lazy_ignored_for_duckdb_table", table=resolved.table)
458
+ gdf = _duckdb_table_to_gdf(
459
+ resolved.db_path, resolved.table, crs=crs or resolved.crs, bbox=bbox
460
+ )
461
+ return gdf
462
+
383
463
  # --- Local file path ---------------------------------------------------
384
464
  if isinstance(resolved, Path):
385
465
  if lazy:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gispulse
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Modular geospatial engine with business rules, triggers, and dual operating modes (DuckDB/PostGIS)
5
5
  Author-email: ImagoData <contact@imagodata.com>
6
6
  License-Expression: AGPL-3.0-or-later
File without changes
File without changes
File without changes
File without changes
File without changes