floe-python 0.5.0__tar.gz → 0.5.2__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 (237) hide show
  1. {floe_python-0.5.0 → floe_python-0.5.2}/Cargo.lock +3 -3
  2. {floe_python-0.5.0 → floe_python-0.5.2}/PKG-INFO +1 -1
  3. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/Cargo.toml +1 -1
  4. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/README.md +5 -0
  5. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/Cargo.toml +2 -2
  6. floe_python-0.5.2/crates/floe-python/pyproject.duckdb.toml +62 -0
  7. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/src/functions.rs +34 -0
  8. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/src/lib.rs +28 -2
  9. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/src/types/outcome.rs +58 -0
  10. {floe_python-0.5.0 → floe_python-0.5.2}/pyproject.toml +1 -1
  11. floe_python-0.5.2/python/floe/__init__.py +222 -0
  12. {floe_python-0.5.0 → floe_python-0.5.2}/python/floe/_floe.pyi +12 -0
  13. floe_python-0.5.0/python/floe/__init__.py +0 -76
  14. {floe_python-0.5.0 → floe_python-0.5.2}/Cargo.toml +0 -0
  15. {floe_python-0.5.0 → floe_python-0.5.2}/README.md +0 -0
  16. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/add_entity.rs +0 -0
  17. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/checks/cast.rs +0 -0
  18. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/checks/mismatch.rs +0 -0
  19. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/checks/mod.rs +0 -0
  20. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/checks/normalize.rs +0 -0
  21. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/checks/not_null.rs +0 -0
  22. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/checks/unique.rs +0 -0
  23. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/catalog.rs +0 -0
  24. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/location.rs +0 -0
  25. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/mod.rs +0 -0
  26. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/parse.rs +0 -0
  27. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/storage.rs +0 -0
  28. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/template.rs +0 -0
  29. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/types.rs +0 -0
  30. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/validate.rs +0 -0
  31. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/config/yaml_decode.rs +0 -0
  32. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/errors.rs +0 -0
  33. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/format.rs +0 -0
  34. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/mod.rs +0 -0
  35. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/avro.rs +0 -0
  36. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/csv.rs +0 -0
  37. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/fixed_width.rs +0 -0
  38. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/json.rs +0 -0
  39. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/json_selector.rs +0 -0
  40. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/mod.rs +0 -0
  41. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/orc.rs +0 -0
  42. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/parquet.rs +0 -0
  43. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/xlsx.rs +0 -0
  44. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/xml.rs +0 -0
  45. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/read/xml_selector.rs +0 -0
  46. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/core/extensions.rs +0 -0
  47. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/core/mod.rs +0 -0
  48. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/core/paths.rs +0 -0
  49. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/core/placement.rs +0 -0
  50. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/core/planner.rs +0 -0
  51. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/core/uri.rs +0 -0
  52. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/core/validation.rs +0 -0
  53. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/mod.rs +0 -0
  54. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/object_store.rs +0 -0
  55. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/ops/archive.rs +0 -0
  56. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/ops/inputs.rs +0 -0
  57. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/ops/mod.rs +0 -0
  58. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/ops/output.rs +0 -0
  59. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/providers/adls.rs +0 -0
  60. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/providers/gcs.rs +0 -0
  61. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/providers/local.rs +0 -0
  62. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/providers/mod.rs +0 -0
  63. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/providers/s3.rs +0 -0
  64. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/storage/target.rs +0 -0
  65. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/unique_seed/mod.rs +0 -0
  66. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/accepted.rs +0 -0
  67. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/arrow_convert.rs +0 -0
  68. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/csv.rs +0 -0
  69. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/delta/commit_metrics.rs +0 -0
  70. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/delta/options.rs +0 -0
  71. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/delta/record_batch.rs +0 -0
  72. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/delta/unity.rs +0 -0
  73. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/delta/unity_tests.rs +0 -0
  74. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/delta.rs +0 -0
  75. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/duckdb/conn.rs +0 -0
  76. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/duckdb/merge.rs +0 -0
  77. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/duckdb.rs +0 -0
  78. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/iceberg/context.rs +0 -0
  79. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/iceberg/data_files.rs +0 -0
  80. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/iceberg/glue.rs +0 -0
  81. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/iceberg/metadata.rs +0 -0
  82. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/iceberg/rest.rs +0 -0
  83. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/iceberg/schema.rs +0 -0
  84. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/iceberg.rs +0 -0
  85. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/metrics.rs +0 -0
  86. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/mod.rs +0 -0
  87. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/parquet.rs +0 -0
  88. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/parts.rs +0 -0
  89. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/sink_format.rs +0 -0
  90. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/strategy/append.rs +0 -0
  91. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/strategy/merge/keys.rs +0 -0
  92. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/strategy/merge/mod.rs +0 -0
  93. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/strategy/merge/scd1.rs +0 -0
  94. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/strategy/merge/scd2.rs +0 -0
  95. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/strategy/merge/shared.rs +0 -0
  96. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/strategy/mod.rs +0 -0
  97. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/io/write/strategy/overwrite.rs +0 -0
  98. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/lib.rs +0 -0
  99. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/lineage/mod.rs +0 -0
  100. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/log.rs +0 -0
  101. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/manifest/builder.rs +0 -0
  102. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/manifest/mod.rs +0 -0
  103. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/manifest/model.rs +0 -0
  104. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/manifest/reconstruct.rs +0 -0
  105. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/profile/mod.rs +0 -0
  106. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/profile/parse.rs +0 -0
  107. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/profile/types.rs +0 -0
  108. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/profile/validate.rs +0 -0
  109. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/report/build.rs +0 -0
  110. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/report/entity.rs +0 -0
  111. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/report/mod.rs +0 -0
  112. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/report/output.rs +0 -0
  113. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/context.rs +0 -0
  114. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/accepted_buffer.rs +0 -0
  115. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/accepted_write.rs +0 -0
  116. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/incremental.rs +0 -0
  117. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/mod.rs +0 -0
  118. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/pii.rs +0 -0
  119. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/precheck.rs +0 -0
  120. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/process.rs +0 -0
  121. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/resolve.rs +0 -0
  122. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/entity/validate_split.rs +0 -0
  123. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/events.rs +0 -0
  124. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/file.rs +0 -0
  125. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/mod.rs +0 -0
  126. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/output.rs +0 -0
  127. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/run/perf.rs +0 -0
  128. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/runner/mod.rs +0 -0
  129. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/runner/outcome.rs +0 -0
  130. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/runtime.rs +0 -0
  131. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/state/mod.rs +0 -0
  132. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/vars/mod.rs +0 -0
  133. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/vars/resolve.rs +0 -0
  134. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/src/warnings.rs +0 -0
  135. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/archive_run.rs +0 -0
  136. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/composite_unique.rs +0 -0
  137. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/delta_run.rs +0 -0
  138. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/dry_run.rs +0 -0
  139. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/duckdb_run.rs +0 -0
  140. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/fixed_width.rs +0 -0
  141. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/iceberg_gcs_run.rs +0 -0
  142. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/iceberg_glue_run.rs +0 -0
  143. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/iceberg_run.rs +0 -0
  144. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/iceberg_s3_run.rs +0 -0
  145. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/json_selectors.rs +0 -0
  146. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/local_run.rs +0 -0
  147. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/mod.rs +0 -0
  148. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/path_normalization.rs +0 -0
  149. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration/run_entities_filter.rs +0 -0
  150. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/integration.rs +0 -0
  151. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/common.rs +0 -0
  152. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/add_entity.rs +0 -0
  153. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/adls_storage.rs +0 -0
  154. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/adls_validation.rs +0 -0
  155. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/catalogs.rs +0 -0
  156. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/config_validation.rs +0 -0
  157. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/duckdb_validation.rs +0 -0
  158. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/gcs_storage.rs +0 -0
  159. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/gcs_validation.rs +0 -0
  160. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/lineage_validation.rs +0 -0
  161. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/local_storage.rs +0 -0
  162. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/mod.rs +0 -0
  163. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/parse.rs +0 -0
  164. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/pii_validation.rs +0 -0
  165. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/remote_base.rs +0 -0
  166. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/storage_resolver_uri.rs +0 -0
  167. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/config/templating.rs +0 -0
  168. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/format.rs +0 -0
  169. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/mod.rs +0 -0
  170. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/avro_input.rs +0 -0
  171. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/csv_nulls.rs +0 -0
  172. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/json_array.rs +0 -0
  173. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/json_ndjson.rs +0 -0
  174. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/json_selector.rs +0 -0
  175. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/mod.rs +0 -0
  176. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/orc_input.rs +0 -0
  177. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/parquet_input.rs +0 -0
  178. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/tsv.rs +0 -0
  179. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/xlsx_input.rs +0 -0
  180. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/xml.rs +0 -0
  181. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/read/xml_selector.rs +0 -0
  182. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/adls.rs +0 -0
  183. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/adls_integration.rs +0 -0
  184. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/gcs.rs +0 -0
  185. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/inputs.rs +0 -0
  186. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/local.rs +0 -0
  187. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/mod.rs +0 -0
  188. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/paths.rs +0 -0
  189. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/planner.rs +0 -0
  190. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/s3.rs +0 -0
  191. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/storage/target.rs +0 -0
  192. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/delta_merge.rs +0 -0
  193. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/delta_write.rs +0 -0
  194. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/iceberg_write.rs +0 -0
  195. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/metrics.rs +0 -0
  196. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/mod.rs +0 -0
  197. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/object_store.rs +0 -0
  198. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/parquet_write.rs +0 -0
  199. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/parts.rs +0 -0
  200. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/io/write/rejected_csv.rs +0 -0
  201. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/manifest/mod.rs +0 -0
  202. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/mod.rs +0 -0
  203. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/profile/mod.rs +0 -0
  204. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/profile/parse.rs +0 -0
  205. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/profile/validate.rs +0 -0
  206. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/report/accepted_output.rs +0 -0
  207. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/report/mod.rs +0 -0
  208. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/report/storage.rs +0 -0
  209. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/check_order.rs +0 -0
  210. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/checks.rs +0 -0
  211. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/entity/accepted_output.rs +0 -0
  212. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/entity/incremental.rs +0 -0
  213. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/entity/mod.rs +0 -0
  214. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/lineage.rs +0 -0
  215. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/mod.rs +0 -0
  216. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/normalize.rs +0 -0
  217. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/pii.rs +0 -0
  218. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/report.rs +0 -0
  219. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/run/schema_mismatch.rs +0 -0
  220. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/runner/adapter.rs +0 -0
  221. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/runner/mod.rs +0 -0
  222. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/state/mod.rs +0 -0
  223. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/vars/mod.rs +0 -0
  224. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit/vars/resolve.rs +0 -0
  225. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-core/tests/unit.rs +0 -0
  226. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/.gitignore +0 -0
  227. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/README.md +0 -0
  228. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/src/observer.rs +0 -0
  229. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/src/types/config.rs +0 -0
  230. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/src/types/errors.rs +0 -0
  231. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/src/types/mod.rs +0 -0
  232. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/tests/fixtures/config.yml +0 -0
  233. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/tests/fixtures/in/customer/customers_valid.csv +0 -0
  234. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/tests/fixtures/invalid_config.yml +0 -0
  235. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/tests/fixtures/profile.yml +0 -0
  236. {floe_python-0.5.0 → floe_python-0.5.2}/crates/floe-python/tests/test_floe.py +0 -0
  237. {floe_python-0.5.0 → floe_python-0.5.2}/python/floe/py.typed +0 -0
@@ -3508,7 +3508,7 @@ dependencies = [
3508
3508
 
3509
3509
  [[package]]
3510
3510
  name = "floe-cli"
3511
- version = "0.5.0"
3511
+ version = "0.5.2"
3512
3512
  dependencies = [
3513
3513
  "assert_cmd",
3514
3514
  "clap",
@@ -3521,7 +3521,7 @@ dependencies = [
3521
3521
 
3522
3522
  [[package]]
3523
3523
  name = "floe-core"
3524
- version = "0.5.0"
3524
+ version = "0.5.2"
3525
3525
  dependencies = [
3526
3526
  "apache-avro 0.16.0",
3527
3527
  "arrow",
@@ -3565,7 +3565,7 @@ dependencies = [
3565
3565
 
3566
3566
  [[package]]
3567
3567
  name = "floe-python"
3568
- version = "0.5.0"
3568
+ version = "0.5.2"
3569
3569
  dependencies = [
3570
3570
  "floe-core",
3571
3571
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: floe-python
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Intended Audience :: Science/Research
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "floe-core"
3
- version = "0.5.0"
3
+ version = "0.5.2"
4
4
  edition = "2021"
5
5
  description = "Core library for Floe, a YAML-driven technical ingestion tool."
6
6
  license = "MIT"
@@ -50,6 +50,11 @@ docker run --rm -v "$PWD:/work" ghcr.io/malon64/floe:latest run -c /work/config.
50
50
  Or download a prebuilt binary from [GitHub Releases](https://github.com/malon64/floe/releases), or `cargo install floe-cli`.
51
51
  → [Full installation guide](docs/installation.md)
52
52
 
53
+ **DuckDB sink** is shipped as a companion (the default artifacts are lean): use the
54
+ `ghcr.io/malon64/floe-duckdb` image, a `floe-duckdb` binary on your `PATH`, or the
55
+ off-PyPI `floe-duckdb` wheel. The lean `floe` auto-delegates DuckDB-sink runs to it.
56
+ → [DuckDB support](docs/installation.md#duckdb-support-companion-distribution)
57
+
53
58
  ## Quick start
54
59
 
55
60
  ```bash
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "floe-python"
3
- version = "0.5.0"
3
+ version = "0.5.2"
4
4
  edition = "2021"
5
5
  description = "Python bindings for floe-core (PyO3-based)"
6
6
  license = "Apache-2.0"
@@ -14,7 +14,7 @@ name = "_floe"
14
14
  crate-type = ["cdylib"]
15
15
 
16
16
  [dependencies]
17
- floe-core = { path = "../floe-core", version = "0.5.0", default-features = false }
17
+ floe-core = { path = "../floe-core", version = "0.5.2", default-features = false }
18
18
  pyo3 = { version = "0.22", features = ["extension-module", "abi3-py310"] }
19
19
  serde_json = "1"
20
20
 
@@ -0,0 +1,62 @@
1
+ # Build manifest for the heavy `floe-duckdb` companion wheel.
2
+ #
3
+ # This builds the SAME `floe-python` crate as `pyproject.toml`, but with the
4
+ # `duckdb` feature on (bundled native DuckDB). The bundled C++ build fails to
5
+ # cross-compile in the manylinux/musllinux wheel containers and exceeds PyPI's
6
+ # 100 MB per-file limit, so this wheel is published OFF PyPI (GitHub Release
7
+ # assets + a PEP 503 index, like PyTorch's own download index) rather than to
8
+ # PyPI.
9
+ #
10
+ # It compiles to a distinct extension module — `floe._floe_duckdb` — and ships
11
+ # ONLY that compiled `.so` (no python-source). That lets it install into the
12
+ # same `floe` package directory as the lean PyPI `floe` wheel without file
13
+ # collisions, so the two coexist and the lean `floe.run` delegates DuckDB sinks
14
+ # to this companion at runtime.
15
+ #
16
+ # Build with: maturin build --release --features duckdb \
17
+ # --manifest-path crates/floe-python/Cargo.toml \
18
+ # -o dist crates/floe-python/pyproject.duckdb.toml
19
+ [build-system]
20
+ requires = ["maturin>=1.7,<2"]
21
+ build-backend = "maturin"
22
+
23
+ [project]
24
+ name = "floe-duckdb"
25
+ version = "0.5.2"
26
+ description = "DuckDB companion extension for floe — adds the native DuckDB sink to the floe Python package"
27
+ readme = "README.md"
28
+ requires-python = ">=3.10"
29
+ license = { text = "Apache-2.0" }
30
+ authors = [{ name = "Floe contributors" }]
31
+ keywords = ["ingestion", "data-quality", "yaml", "polars", "duckdb"]
32
+ classifiers = [
33
+ "Development Status :: 4 - Beta",
34
+ "Intended Audience :: Developers",
35
+ "Intended Audience :: Science/Research",
36
+ "Programming Language :: Rust",
37
+ "Programming Language :: Python :: 3",
38
+ "Programming Language :: Python :: 3 :: Only",
39
+ "Programming Language :: Python :: 3.10",
40
+ "Programming Language :: Python :: 3.11",
41
+ "Programming Language :: Python :: 3.12",
42
+ "Programming Language :: Python :: 3.13",
43
+ "Topic :: Scientific/Engineering :: Information Analysis",
44
+ ]
45
+ # The lean `floe` wheel provides the Python source and public API; this companion
46
+ # only contributes the compiled DuckDB extension module into that package. Pin to
47
+ # the exact lean version so the companion's `_floe_duckdb` ABI always matches the
48
+ # `floe` package it plugs into.
49
+ dependencies = ["floe-python==0.5.2"]
50
+
51
+ [project.urls]
52
+ Homepage = "https://github.com/malon64/floe"
53
+ Repository = "https://github.com/malon64/floe"
54
+ "Bug Tracker" = "https://github.com/malon64/floe/issues"
55
+
56
+ [tool.maturin]
57
+ # Compiled extension is installed as floe._floe_duckdb (matches the
58
+ # `#[pymodule] fn _floe_duckdb` entry point gated behind the `duckdb` feature).
59
+ # No python-source: ship only the .so so this coexists with the lean wheel.
60
+ module-name = "floe._floe_duckdb"
61
+ manifest-path = "Cargo.toml"
62
+ features = ["duckdb"]
@@ -105,6 +105,40 @@ pub fn load_config(config_path: &str) -> PyResult<PyRootConfig> {
105
105
  .map_err(to_py_err)
106
106
  }
107
107
 
108
+ /// True when the config writes to a DuckDB *accepted* sink. The lean `floe`
109
+ /// Python wrapper calls this to decide whether a run must be delegated to the
110
+ /// `floe._floe_duckdb` companion module. Only the accepted sink can target
111
+ /// DuckDB: rejected sinks accept `csv` only, so a `rejected.format: duckdb` is
112
+ /// always invalid and must fall through to the lean run path so its real
113
+ /// unsupported-rejected-sink validation error surfaces, rather than being
114
+ /// delegated (which would yield a misleading companion-install hint). The same
115
+ /// `profile_vars` / `profile_path` the run will use are applied first, so a sink
116
+ /// format selected through a profile variable (e.g. `format: "{{SINK_FORMAT}}"`)
117
+ /// is detected and delegated rather than silently run on the lean build. A load
118
+ /// failure returns `false` so the lean run path surfaces the real error rather
119
+ /// than masking it.
120
+ #[pyfunction]
121
+ #[pyo3(signature = (config_path, profile_vars=None, profile_path=None))]
122
+ pub fn config_targets_duckdb(
123
+ config_path: &str,
124
+ profile_vars: Option<HashMap<String, String>>,
125
+ profile_path: Option<String>,
126
+ ) -> bool {
127
+ let path = Path::new(config_path);
128
+ let vars = match load_optional_profile(profile_path, profile_vars) {
129
+ Ok(Some(profile)) => profile.variables,
130
+ Ok(None) => HashMap::new(),
131
+ Err(_) => return false,
132
+ };
133
+ match floe_core::load_config_with_profile_vars(path, &vars) {
134
+ Ok(config) => config
135
+ .entities
136
+ .iter()
137
+ .any(|entity| entity.sink.accepted.format == "duckdb"),
138
+ Err(_) => false,
139
+ }
140
+ }
141
+
108
142
  #[pyfunction]
109
143
  pub fn extract_config_env_vars(config_path: &str) -> PyResult<HashMap<String, String>> {
110
144
  let path = Path::new(config_path);
@@ -11,8 +11,13 @@ use types::config::{PyEntityConfig, PyRootConfig};
11
11
  use types::errors::{FloeConfigError, FloeError, FloeIoError, FloeRunError, FloeStorageError};
12
12
  use types::outcome::PyRunOutcome;
13
13
 
14
- #[pymodule]
15
- fn _floe(m: &Bound<'_, PyModule>) -> PyResult<()> {
14
+ // Shared registration for both the lean (`_floe`) and full (`_floe_duckdb`)
15
+ // extension modules. The two entry points below differ only in name so the
16
+ // compiled symbol (`PyInit__floe` vs `PyInit__floe_duckdb`) matches the dotted
17
+ // module each wheel installs; everything they expose is identical save for the
18
+ // `HAS_DUCKDB` capability flag the lean Python wrapper uses to decide whether to
19
+ // delegate a DuckDB-sink run to the companion module.
20
+ fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
16
21
  // Exception hierarchy
17
22
  m.add("FloeError", m.py().get_type_bound::<FloeError>())?;
18
23
  m.add(
@@ -26,6 +31,10 @@ fn _floe(m: &Bound<'_, PyModule>) -> PyResult<()> {
26
31
  )?;
27
32
  m.add("FloeIoError", m.py().get_type_bound::<FloeIoError>())?;
28
33
 
34
+ // Capability flag: true only in the `+duckdb` build. The lean Python wrapper
35
+ // reads this to know whether it must delegate DuckDB sinks to the companion.
36
+ m.add("HAS_DUCKDB", cfg!(feature = "duckdb"))?;
37
+
29
38
  // Types
30
39
  m.add_class::<PyRootConfig>()?;
31
40
  m.add_class::<PyEntityConfig>()?;
@@ -38,6 +47,7 @@ fn _floe(m: &Bound<'_, PyModule>) -> PyResult<()> {
38
47
  m.add_function(wrap_pyfunction!(functions::extract_config_env_vars, m)?)?;
39
48
  m.add_function(wrap_pyfunction!(functions::inspect_entity_state, m)?)?;
40
49
  m.add_function(wrap_pyfunction!(functions::reset_entity_state, m)?)?;
50
+ m.add_function(wrap_pyfunction!(functions::config_targets_duckdb, m)?)?;
41
51
 
42
52
  // Observer
43
53
  m.add_function(wrap_pyfunction!(observer::set_observer, m)?)?;
@@ -45,3 +55,19 @@ fn _floe(m: &Bound<'_, PyModule>) -> PyResult<()> {
45
55
 
46
56
  Ok(())
47
57
  }
58
+
59
+ /// Lean extension module, installed as `floe._floe` by the PyPI `floe` wheel.
60
+ #[cfg(not(feature = "duckdb"))]
61
+ #[pymodule]
62
+ fn _floe(m: &Bound<'_, PyModule>) -> PyResult<()> {
63
+ register(m)
64
+ }
65
+
66
+ /// Full extension module, installed as `floe._floe_duckdb` by the off-PyPI
67
+ /// `floe-duckdb` companion wheel. Ships only the compiled `.so` into the shared
68
+ /// `floe` package so it can coexist with the lean wheel.
69
+ #[cfg(feature = "duckdb")]
70
+ #[pymodule]
71
+ fn _floe_duckdb(m: &Bound<'_, PyModule>) -> PyResult<()> {
72
+ register(m)
73
+ }
@@ -48,6 +48,24 @@ impl From<DryRunEntityPreview> for DryRunPreviewData {
48
48
  }
49
49
  }
50
50
 
51
+ impl DryRunPreviewData {
52
+ fn from_dict(d: &Bound<'_, PyAny>) -> PyResult<Self> {
53
+ Ok(DryRunPreviewData {
54
+ name: d.get_item("name")?.extract()?,
55
+ input_path: d.get_item("input_path")?.extract()?,
56
+ input_format: d.get_item("input_format")?.extract()?,
57
+ accepted_path: d.get_item("accepted_path")?.extract()?,
58
+ accepted_format: d.get_item("accepted_format")?.extract()?,
59
+ rejected_path: d.get_item("rejected_path")?.extract()?,
60
+ rejected_format: d.get_item("rejected_format")?.extract()?,
61
+ archive_path: d.get_item("archive_path")?.extract()?,
62
+ archive_storage: d.get_item("archive_storage")?.extract()?,
63
+ report_file: d.get_item("report_file")?.extract()?,
64
+ scanned_files: d.get_item("scanned_files")?.extract()?,
65
+ })
66
+ }
67
+ }
68
+
51
69
  impl TryFrom<RunOutcome> for PyRunOutcome {
52
70
  type Error = serde_json::Error;
53
71
 
@@ -79,6 +97,46 @@ fn json_loads<'py>(py: Python<'py>, s: &str) -> PyResult<Bound<'py, PyAny>> {
79
97
 
80
98
  #[pymethods]
81
99
  impl PyRunOutcome {
100
+ /// Rebuild a lean `RunOutcome` from the `to_dict()` payload of a companion's
101
+ /// `RunOutcome`. The lean `_floe` and companion `_floe_duckdb` are separate
102
+ /// native libraries, so a companion-returned outcome is a *different* Python
103
+ /// class. Delegated DuckDB runs round-trip through this so callers always get
104
+ /// the lean `RunOutcome` type they imported, not the companion's.
105
+ #[staticmethod]
106
+ fn from_dict(data: Bound<'_, PyAny>) -> PyResult<Self> {
107
+ let py = data.py();
108
+ let json = py.import_bound("json")?;
109
+ let dumps = |obj: Bound<'_, PyAny>| -> PyResult<String> {
110
+ json.call_method1("dumps", (obj,))?.extract()
111
+ };
112
+ let run_id: String = data.get_item("run_id")?.extract()?;
113
+ let report_base_path: Option<String> = data.get_item("report_base_path")?.extract()?;
114
+ let dry_run: bool = data.get_item("dry_run")?.extract()?;
115
+ let summary_json = dumps(data.get_item("summary")?)?;
116
+ let mut entity_reports_json = Vec::new();
117
+ for item in data.get_item("entity_reports")?.iter()? {
118
+ entity_reports_json.push(dumps(item?)?);
119
+ }
120
+ let previews = data.get_item("dry_run_previews")?;
121
+ let dry_run_previews_data = if previews.is_none() {
122
+ None
123
+ } else {
124
+ let mut v = Vec::new();
125
+ for item in previews.iter()? {
126
+ v.push(DryRunPreviewData::from_dict(&item?)?);
127
+ }
128
+ Some(v)
129
+ };
130
+ Ok(PyRunOutcome {
131
+ run_id,
132
+ report_base_path,
133
+ dry_run,
134
+ summary_json,
135
+ entity_reports_json,
136
+ dry_run_previews_data,
137
+ })
138
+ }
139
+
82
140
  #[getter]
83
141
  fn summary(&self, py: Python<'_>) -> PyResult<PyObject> {
84
142
  Ok(json_loads(py, &self.summary_json)?.into_py(py))
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "floe-python"
7
- version = "0.5.0"
7
+ version = "0.5.2"
8
8
  description = "Python bindings for floe-core — run floe pipelines at Rust speed from Python and notebooks"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,222 @@
1
+ """
2
+ floe — Python bindings for the floe ingestion engine.
3
+
4
+ Run floe pipelines at Rust speed from Python notebooks::
5
+
6
+ import floe
7
+
8
+ # Live progress
9
+ floe.set_observer(lambda e: print(f"[{e['event']}]", e.get("name", "")))
10
+
11
+ # Validate config
12
+ floe.validate("pipeline.yml")
13
+
14
+ # Execute pipeline
15
+ outcome = floe.run("pipeline.yml", entities=["customers"])
16
+ print(outcome.summary["results"])
17
+
18
+ # Dry run
19
+ outcome = floe.run("pipeline.yml", dry_run=True)
20
+ for p in outcome.dry_run_previews:
21
+ print(p["name"], p["scanned_files"])
22
+
23
+ # Inspect incremental state
24
+ state = floe.inspect_entity_state("pipeline.yml", "customers")
25
+ print(state["incremental_mode"], state["state"])
26
+
27
+ # Use a profile (with catalog / variable overrides)
28
+ floe.run("pipeline.yml", profile_path="profile.yml")
29
+ floe.run("pipeline.yml", profile_vars={"output_bucket": "s3://my-bucket"})
30
+ """
31
+
32
+ import importlib
33
+ import importlib.util
34
+ import sys
35
+ from importlib.metadata import version as _pkg_version
36
+
37
+ try:
38
+ __version__ = _pkg_version("floe-python")
39
+ except Exception:
40
+ __version__ = "unknown"
41
+
42
+ from floe._floe import (
43
+ FloeError,
44
+ FloeConfigError,
45
+ FloeRunError,
46
+ FloeStorageError,
47
+ FloeIoError,
48
+ RootConfig,
49
+ EntityConfig,
50
+ RunOutcome,
51
+ validate,
52
+ run as _run,
53
+ load_config,
54
+ extract_config_env_vars,
55
+ inspect_entity_state,
56
+ reset_entity_state,
57
+ set_observer as _lean_set_observer,
58
+ clear_observer as _lean_clear_observer,
59
+ )
60
+ from floe import _floe as _lean
61
+
62
+ # DuckDB sinks compile a bundled native build that is too large for PyPI, so the
63
+ # published `floe` wheel is lean. DuckDB support ships as a separate off-PyPI
64
+ # `floe-duckdb` wheel that installs a `floe._floe_duckdb` companion extension into
65
+ # this same package. When a run targets a DuckDB sink and this is the lean build,
66
+ # `run` transparently delegates to that companion, mirroring the lean `floe` CLI
67
+ # re-execing the `floe-duckdb` binary. Install instructions for the companion:
68
+ # `--index-url` (not `--extra-index-url`) so pip resolves the `floe-duckdb`
69
+ # name ONLY from the off-PyPI index: `floe-duckdb` is intentionally absent from
70
+ # PyPI, and `--extra-index-url` would leave PyPI in play, letting a squatted /
71
+ # higher-version `floe-duckdb` there shadow the real companion (dependency
72
+ # confusion). The companion's `floe-python` dependency is already satisfied
73
+ # here — this hint only fires from an installed lean `floe` — so replacing PyPI
74
+ # for this single install does not break dependency resolution.
75
+ _DUCKDB_INSTALL_HINT = (
76
+ "this config writes to a DuckDB sink, but the installed `floe` wheel is the "
77
+ "lean build without DuckDB support and the `floe-duckdb` companion wheel is "
78
+ "not installed. Install it from the off-PyPI index, e.g. "
79
+ "`pip install floe-duckdb --index-url "
80
+ "https://malon64.github.io/floe/simple/`, or use the "
81
+ "`ghcr.io/malon64/floe-duckdb` image."
82
+ )
83
+
84
+
85
+ # The lean `_floe` and companion `_floe_duckdb` are separate native libraries
86
+ # with independent observer state, so the registered callback must be mirrored
87
+ # into the companion before a delegated run or its progress/log events are lost.
88
+ # Track the active observer here so delegation can re-install it.
89
+ _current_observer = None
90
+
91
+
92
+ def set_observer(callback):
93
+ """Register a callback for live run/validation events.
94
+
95
+ Mirrors into the DuckDB companion only if it is *already* loaded, so
96
+ delegated DuckDB runs emit the same events as native runs. Registering an
97
+ observer must not trigger a companion import: if the companion is installed
98
+ but its native extension fails to load, that error should surface when a
99
+ DuckDB run is actually attempted, not as a side effect of setting an
100
+ observer. `run` re-installs the tracked observer into the companion right
101
+ before delegating, so a companion loaded later still gets the callback.
102
+ """
103
+ global _current_observer
104
+ _current_observer = callback
105
+ companion = _loaded_duckdb_module()
106
+ if companion is not None:
107
+ companion.set_observer(callback)
108
+ return _lean_set_observer(callback)
109
+
110
+
111
+ def clear_observer():
112
+ """Remove the previously registered event callback."""
113
+ global _current_observer
114
+ _current_observer = None
115
+ companion = _loaded_duckdb_module()
116
+ if companion is not None:
117
+ companion.clear_observer()
118
+ return _lean_clear_observer()
119
+
120
+
121
+ def _loaded_duckdb_module():
122
+ """Return the companion module only if it is already imported, else None.
123
+
124
+ Unlike `_duckdb_module`, this never triggers an import, so it cannot raise a
125
+ companion load error. Used on paths (observer registration) where a broken
126
+ companion must not surface until a DuckDB run is actually attempted.
127
+ """
128
+ return sys.modules.get("floe._floe_duckdb")
129
+
130
+
131
+ def _duckdb_module():
132
+ """Return the `floe._floe_duckdb` companion extension, or None if not installed.
133
+
134
+ A genuinely absent companion maps to None. If the companion IS installed but
135
+ its native extension fails to load (incompatible libc, unresolved symbol), the
136
+ loader error is allowed to propagate so it surfaces instead of a misleading
137
+ "companion wheel is not installed" hint. `find_spec` distinguishes the two:
138
+ `from floe import _floe_duckdb` would raise a plain ImportError for the absent
139
+ case too (attribute fallback), so the exception type alone cannot tell them
140
+ apart — but `find_spec` returns None only when the module is truly not there.
141
+ """
142
+ if importlib.util.find_spec("floe._floe_duckdb") is None:
143
+ return None
144
+ return importlib.import_module("floe._floe_duckdb")
145
+
146
+
147
+ def run(config_path, *args, **kwargs):
148
+ """Execute a floe pipeline.
149
+
150
+ Transparently delegates to the `floe-duckdb` companion when the config
151
+ targets a DuckDB sink and this lean build lacks native DuckDB support.
152
+ """
153
+ # Dry runs resolve inputs and return previews before any sink write, so they
154
+ # never touch the DuckDB writer — run them on the lean build without the
155
+ # companion. Positional order mirrors the native `run`:
156
+ # (config_path, entities, dry_run, run_id, full_refresh, profile_vars, profile_path).
157
+ dry_run = kwargs.get("dry_run", args[1] if len(args) >= 2 else False)
158
+ # The sink format can be selected through a profile variable (e.g.
159
+ # `format: "{{SINK_FORMAT}}"`), so the delegation check must apply the same
160
+ # profile inputs as the run or it would miss the DuckDB sink and run lean.
161
+ profile_vars = kwargs.get("profile_vars", args[4] if len(args) >= 5 else None)
162
+ profile_path = kwargs.get("profile_path", args[5] if len(args) >= 6 else None)
163
+ if (
164
+ not dry_run
165
+ and not getattr(_lean, "HAS_DUCKDB", False)
166
+ and _lean.config_targets_duckdb(
167
+ config_path, profile_vars=profile_vars, profile_path=profile_path
168
+ )
169
+ ):
170
+ companion = _duckdb_module()
171
+ if companion is None:
172
+ raise FloeConfigError(_DUCKDB_INSTALL_HINT)
173
+ if _current_observer is not None:
174
+ companion.set_observer(_current_observer)
175
+ try:
176
+ outcome = companion.run(config_path, *args, **kwargs)
177
+ except companion.FloeError as exc:
178
+ raise _translate_companion_error(exc) from exc
179
+ # The companion is a separate native library, so its RunOutcome is a
180
+ # distinct Python class. Round-trip through the dict form so callers get
181
+ # the lean `RunOutcome` type they imported from `floe`.
182
+ return RunOutcome.from_dict(outcome.to_dict())
183
+ return _run(config_path, *args, **kwargs)
184
+
185
+
186
+ # The companion's exception classes are distinct objects from the lean ones
187
+ # (separate native libraries), so `except floe.FloeRunError` on a caller's side
188
+ # would not catch a companion-raised error. Map each companion exception to the
189
+ # matching lean class by name so delegated runs raise the types callers expect.
190
+ _LEAN_EXCEPTIONS = {
191
+ "FloeConfigError": FloeConfigError,
192
+ "FloeRunError": FloeRunError,
193
+ "FloeStorageError": FloeStorageError,
194
+ "FloeIoError": FloeIoError,
195
+ "FloeError": FloeError,
196
+ }
197
+
198
+
199
+ def _translate_companion_error(exc):
200
+ lean_cls = _LEAN_EXCEPTIONS.get(type(exc).__name__, FloeError)
201
+ return lean_cls(str(exc))
202
+
203
+
204
+ __all__ = [
205
+ "__version__",
206
+ "FloeError",
207
+ "FloeConfigError",
208
+ "FloeRunError",
209
+ "FloeStorageError",
210
+ "FloeIoError",
211
+ "RootConfig",
212
+ "EntityConfig",
213
+ "RunOutcome",
214
+ "validate",
215
+ "run",
216
+ "load_config",
217
+ "extract_config_env_vars",
218
+ "inspect_entity_state",
219
+ "reset_entity_state",
220
+ "set_observer",
221
+ "clear_observer",
222
+ ]
@@ -143,10 +143,22 @@ class RunOutcome:
143
143
  def _repr_html_(self) -> str: ...
144
144
  def __repr__(self) -> str: ...
145
145
 
146
+ # ---------------------------------------------------------------------------
147
+ # Capability flags
148
+ # ---------------------------------------------------------------------------
149
+
150
+ HAS_DUCKDB: bool
151
+ """True only in the `+duckdb` build (the off-PyPI `floe-duckdb` companion)."""
152
+
146
153
  # ---------------------------------------------------------------------------
147
154
  # Core functions
148
155
  # ---------------------------------------------------------------------------
149
156
 
157
+ def config_targets_duckdb(config_path: str) -> bool:
158
+ """True when the config writes to a DuckDB sink. Returns False if the config
159
+ cannot be loaded, so the normal run path surfaces the real error."""
160
+ ...
161
+
150
162
  def validate(
151
163
  config_path: str,
152
164
  entities: list[str] | None = None,
@@ -1,76 +0,0 @@
1
- """
2
- floe — Python bindings for the floe ingestion engine.
3
-
4
- Run floe pipelines at Rust speed from Python notebooks::
5
-
6
- import floe
7
-
8
- # Live progress
9
- floe.set_observer(lambda e: print(f"[{e['event']}]", e.get("name", "")))
10
-
11
- # Validate config
12
- floe.validate("pipeline.yml")
13
-
14
- # Execute pipeline
15
- outcome = floe.run("pipeline.yml", entities=["customers"])
16
- print(outcome.summary["results"])
17
-
18
- # Dry run
19
- outcome = floe.run("pipeline.yml", dry_run=True)
20
- for p in outcome.dry_run_previews:
21
- print(p["name"], p["scanned_files"])
22
-
23
- # Inspect incremental state
24
- state = floe.inspect_entity_state("pipeline.yml", "customers")
25
- print(state["incremental_mode"], state["state"])
26
-
27
- # Use a profile (with catalog / variable overrides)
28
- floe.run("pipeline.yml", profile_path="profile.yml")
29
- floe.run("pipeline.yml", profile_vars={"output_bucket": "s3://my-bucket"})
30
- """
31
-
32
- from importlib.metadata import version as _pkg_version
33
-
34
- try:
35
- __version__ = _pkg_version("floe-python")
36
- except Exception:
37
- __version__ = "unknown"
38
-
39
- from floe._floe import (
40
- FloeError,
41
- FloeConfigError,
42
- FloeRunError,
43
- FloeStorageError,
44
- FloeIoError,
45
- RootConfig,
46
- EntityConfig,
47
- RunOutcome,
48
- validate,
49
- run,
50
- load_config,
51
- extract_config_env_vars,
52
- inspect_entity_state,
53
- reset_entity_state,
54
- set_observer,
55
- clear_observer,
56
- )
57
-
58
- __all__ = [
59
- "__version__",
60
- "FloeError",
61
- "FloeConfigError",
62
- "FloeRunError",
63
- "FloeStorageError",
64
- "FloeIoError",
65
- "RootConfig",
66
- "EntityConfig",
67
- "RunOutcome",
68
- "validate",
69
- "run",
70
- "load_config",
71
- "extract_config_env_vars",
72
- "inspect_entity_state",
73
- "reset_entity_state",
74
- "set_observer",
75
- "clear_observer",
76
- ]
File without changes
File without changes