floe-python 0.6.9__tar.gz → 0.6.11__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 (241) hide show
  1. {floe_python-0.6.9 → floe_python-0.6.11}/Cargo.lock +3 -3
  2. {floe_python-0.6.9 → floe_python-0.6.11}/PKG-INFO +1 -1
  3. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/Cargo.toml +1 -1
  4. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/lineage/mod.rs +100 -2
  5. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/manifest/reconstruct.rs +69 -0
  6. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/lineage.rs +291 -0
  7. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/Cargo.toml +2 -2
  8. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/pyproject.duckdb.toml +2 -2
  9. {floe_python-0.6.9 → floe_python-0.6.11}/pyproject.toml +1 -1
  10. {floe_python-0.6.9 → floe_python-0.6.11}/Cargo.toml +0 -0
  11. {floe_python-0.6.9 → floe_python-0.6.11}/README.md +0 -0
  12. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/README.md +0 -0
  13. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/add_entity.rs +0 -0
  14. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/checks/cast.rs +0 -0
  15. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/checks/mismatch.rs +0 -0
  16. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/checks/mod.rs +0 -0
  17. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/checks/normalize.rs +0 -0
  18. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/checks/not_null.rs +0 -0
  19. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/checks/unique.rs +0 -0
  20. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/catalog.rs +0 -0
  21. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/location.rs +0 -0
  22. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/mod.rs +0 -0
  23. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/parse.rs +0 -0
  24. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/storage.rs +0 -0
  25. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/template.rs +0 -0
  26. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/types.rs +0 -0
  27. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/validate.rs +0 -0
  28. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/config/yaml_decode.rs +0 -0
  29. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/errors.rs +0 -0
  30. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/format.rs +0 -0
  31. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/mod.rs +0 -0
  32. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/avro.rs +0 -0
  33. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/csv.rs +0 -0
  34. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/fixed_width.rs +0 -0
  35. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/json.rs +0 -0
  36. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/json_selector.rs +0 -0
  37. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/mod.rs +0 -0
  38. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/orc.rs +0 -0
  39. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/parquet.rs +0 -0
  40. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/xlsx.rs +0 -0
  41. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/xml.rs +0 -0
  42. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/read/xml_selector.rs +0 -0
  43. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/core/extensions.rs +0 -0
  44. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/core/mod.rs +0 -0
  45. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/core/paths.rs +0 -0
  46. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/core/placement.rs +0 -0
  47. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/core/planner.rs +0 -0
  48. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/core/uri.rs +0 -0
  49. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/core/validation.rs +0 -0
  50. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/mod.rs +0 -0
  51. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/object_store.rs +0 -0
  52. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/ops/archive.rs +0 -0
  53. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/ops/inputs.rs +0 -0
  54. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/ops/mod.rs +0 -0
  55. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/ops/output.rs +0 -0
  56. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/providers/adls.rs +0 -0
  57. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/providers/gcs.rs +0 -0
  58. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/providers/local.rs +0 -0
  59. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/providers/mod.rs +0 -0
  60. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/providers/s3.rs +0 -0
  61. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/storage/target.rs +0 -0
  62. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/unique_seed/mod.rs +0 -0
  63. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/accepted.rs +0 -0
  64. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/arrow_convert.rs +0 -0
  65. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/csv.rs +0 -0
  66. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/delta/commit_metrics.rs +0 -0
  67. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/delta/options.rs +0 -0
  68. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/delta/record_batch.rs +0 -0
  69. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/delta/unity.rs +0 -0
  70. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/delta/unity_tests.rs +0 -0
  71. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/delta.rs +0 -0
  72. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/duckdb/conn.rs +0 -0
  73. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/duckdb/merge.rs +0 -0
  74. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/duckdb.rs +0 -0
  75. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/iceberg/context.rs +0 -0
  76. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/iceberg/data_files.rs +0 -0
  77. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/iceberg/glue.rs +0 -0
  78. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/iceberg/metadata.rs +0 -0
  79. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/iceberg/rest.rs +0 -0
  80. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/iceberg/schema.rs +0 -0
  81. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/iceberg.rs +0 -0
  82. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/metrics.rs +0 -0
  83. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/mod.rs +0 -0
  84. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/parquet.rs +0 -0
  85. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/parts.rs +0 -0
  86. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/sink_format.rs +0 -0
  87. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/strategy/append.rs +0 -0
  88. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/strategy/merge/keys.rs +0 -0
  89. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/strategy/merge/mod.rs +0 -0
  90. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/strategy/merge/scd1.rs +0 -0
  91. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/strategy/merge/scd2.rs +0 -0
  92. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/strategy/merge/shared.rs +0 -0
  93. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/strategy/mod.rs +0 -0
  94. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/io/write/strategy/overwrite.rs +0 -0
  95. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/lib.rs +0 -0
  96. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/log.rs +0 -0
  97. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/manifest/builder.rs +0 -0
  98. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/manifest/mod.rs +0 -0
  99. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/manifest/model.rs +0 -0
  100. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/manifest/runtime.rs +0 -0
  101. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/profile/mod.rs +0 -0
  102. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/profile/parse.rs +0 -0
  103. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/profile/types.rs +0 -0
  104. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/profile/validate.rs +0 -0
  105. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/report/build.rs +0 -0
  106. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/report/entity.rs +0 -0
  107. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/report/mod.rs +0 -0
  108. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/report/output.rs +0 -0
  109. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/context.rs +0 -0
  110. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/accepted_buffer.rs +0 -0
  111. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/accepted_write.rs +0 -0
  112. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/incremental.rs +0 -0
  113. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/mod.rs +0 -0
  114. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/pii.rs +0 -0
  115. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/precheck.rs +0 -0
  116. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/process.rs +0 -0
  117. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/resolve.rs +0 -0
  118. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/entity/validate_split.rs +0 -0
  119. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/events.rs +0 -0
  120. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/file.rs +0 -0
  121. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/mod.rs +0 -0
  122. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/output.rs +0 -0
  123. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/run/perf.rs +0 -0
  124. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/runner/mod.rs +0 -0
  125. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/runner/outcome.rs +0 -0
  126. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/runtime.rs +0 -0
  127. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/secret.rs +0 -0
  128. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/state/mod.rs +0 -0
  129. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/vars/mod.rs +0 -0
  130. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/vars/resolve.rs +0 -0
  131. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/src/warnings.rs +0 -0
  132. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/archive_run.rs +0 -0
  133. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/composite_unique.rs +0 -0
  134. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/delta_run.rs +0 -0
  135. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/dry_run.rs +0 -0
  136. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/duckdb_run.rs +0 -0
  137. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/fixed_width.rs +0 -0
  138. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/iceberg_gcs_run.rs +0 -0
  139. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/iceberg_glue_run.rs +0 -0
  140. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/iceberg_run.rs +0 -0
  141. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/iceberg_s3_run.rs +0 -0
  142. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/json_selectors.rs +0 -0
  143. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/local_run.rs +0 -0
  144. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/mod.rs +0 -0
  145. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/path_normalization.rs +0 -0
  146. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration/run_entities_filter.rs +0 -0
  147. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/integration.rs +0 -0
  148. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/common.rs +0 -0
  149. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/add_entity.rs +0 -0
  150. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/adls_storage.rs +0 -0
  151. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/adls_validation.rs +0 -0
  152. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/catalogs.rs +0 -0
  153. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/config_validation.rs +0 -0
  154. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/duckdb_validation.rs +0 -0
  155. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/gcs_storage.rs +0 -0
  156. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/gcs_validation.rs +0 -0
  157. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/lineage_validation.rs +0 -0
  158. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/local_storage.rs +0 -0
  159. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/location.rs +0 -0
  160. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/mod.rs +0 -0
  161. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/parse.rs +0 -0
  162. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/pii_validation.rs +0 -0
  163. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/remote_base.rs +0 -0
  164. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/storage_resolver_uri.rs +0 -0
  165. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/config/templating.rs +0 -0
  166. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/format.rs +0 -0
  167. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/mod.rs +0 -0
  168. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/avro_input.rs +0 -0
  169. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/csv_nulls.rs +0 -0
  170. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/json_array.rs +0 -0
  171. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/json_ndjson.rs +0 -0
  172. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/json_selector.rs +0 -0
  173. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/mod.rs +0 -0
  174. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/orc_input.rs +0 -0
  175. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/parquet_input.rs +0 -0
  176. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/tsv.rs +0 -0
  177. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/xlsx_input.rs +0 -0
  178. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/xml.rs +0 -0
  179. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/read/xml_selector.rs +0 -0
  180. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/adls.rs +0 -0
  181. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/adls_integration.rs +0 -0
  182. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/gcs.rs +0 -0
  183. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/inputs.rs +0 -0
  184. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/local.rs +0 -0
  185. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/mod.rs +0 -0
  186. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/paths.rs +0 -0
  187. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/planner.rs +0 -0
  188. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/s3.rs +0 -0
  189. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/storage/target.rs +0 -0
  190. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/delta_merge.rs +0 -0
  191. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/delta_write.rs +0 -0
  192. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/iceberg_write.rs +0 -0
  193. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/metrics.rs +0 -0
  194. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/mod.rs +0 -0
  195. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/object_store.rs +0 -0
  196. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/parquet_write.rs +0 -0
  197. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/parts.rs +0 -0
  198. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/io/write/rejected_csv.rs +0 -0
  199. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/manifest/mod.rs +0 -0
  200. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/manifest/runtime.rs +0 -0
  201. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/mod.rs +0 -0
  202. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/profile/mod.rs +0 -0
  203. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/profile/parse.rs +0 -0
  204. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/profile/validate.rs +0 -0
  205. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/report/accepted_output.rs +0 -0
  206. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/report/mod.rs +0 -0
  207. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/report/storage.rs +0 -0
  208. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/check_order.rs +0 -0
  209. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/checks.rs +0 -0
  210. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/entity/accepted_output.rs +0 -0
  211. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/entity/incremental.rs +0 -0
  212. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/entity/mod.rs +0 -0
  213. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/mod.rs +0 -0
  214. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/normalize.rs +0 -0
  215. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/pii.rs +0 -0
  216. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/replay_base.rs +0 -0
  217. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/report.rs +0 -0
  218. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/run/schema_mismatch.rs +0 -0
  219. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/runner/adapter.rs +0 -0
  220. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/runner/mod.rs +0 -0
  221. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/state/mod.rs +0 -0
  222. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/vars/mod.rs +0 -0
  223. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit/vars/resolve.rs +0 -0
  224. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-core/tests/unit.rs +0 -0
  225. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/.gitignore +0 -0
  226. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/README.md +0 -0
  227. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/src/functions.rs +0 -0
  228. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/src/lib.rs +0 -0
  229. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/src/observer.rs +0 -0
  230. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/src/types/config.rs +0 -0
  231. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/src/types/errors.rs +0 -0
  232. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/src/types/mod.rs +0 -0
  233. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/src/types/outcome.rs +0 -0
  234. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/tests/fixtures/config.yml +0 -0
  235. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/tests/fixtures/in/customer/customers_valid.csv +0 -0
  236. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/tests/fixtures/invalid_config.yml +0 -0
  237. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/tests/fixtures/profile.yml +0 -0
  238. {floe_python-0.6.9 → floe_python-0.6.11}/crates/floe-python/tests/test_floe.py +0 -0
  239. {floe_python-0.6.9 → floe_python-0.6.11}/python/floe/__init__.py +0 -0
  240. {floe_python-0.6.9 → floe_python-0.6.11}/python/floe/_floe.pyi +0 -0
  241. {floe_python-0.6.9 → floe_python-0.6.11}/python/floe/py.typed +0 -0
@@ -3340,7 +3340,7 @@ dependencies = [
3340
3340
 
3341
3341
  [[package]]
3342
3342
  name = "floe-cli"
3343
- version = "0.6.9"
3343
+ version = "0.6.11"
3344
3344
  dependencies = [
3345
3345
  "assert_cmd",
3346
3346
  "clap",
@@ -3353,7 +3353,7 @@ dependencies = [
3353
3353
 
3354
3354
  [[package]]
3355
3355
  name = "floe-core"
3356
- version = "0.6.9"
3356
+ version = "0.6.11"
3357
3357
  dependencies = [
3358
3358
  "apache-avro",
3359
3359
  "arrow",
@@ -3399,7 +3399,7 @@ dependencies = [
3399
3399
 
3400
3400
  [[package]]
3401
3401
  name = "floe-python"
3402
- version = "0.6.9"
3402
+ version = "0.6.11"
3403
3403
  dependencies = [
3404
3404
  "floe-core",
3405
3405
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: floe-python
3
- Version: 0.6.9
3
+ Version: 0.6.11
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.6.9"
3
+ version = "0.6.11"
4
4
  edition = "2021"
5
5
  description = "Core library for Floe, a YAML-driven technical ingestion tool."
6
6
  license = "MIT"
@@ -8,12 +8,15 @@ use uuid::Uuid;
8
8
 
9
9
  use crate::config::{EntityConfig, LineageConfig};
10
10
  use crate::run::events::{RunEvent, RunObserver};
11
+ use crate::secret::Secret;
11
12
 
12
13
  const DEFAULT_PRODUCER: &str = concat!(
13
14
  "https://github.com/malon64/floe/releases/tag/v",
14
15
  env!("CARGO_PKG_VERSION")
15
16
  );
16
17
 
18
+ const OPENLINEAGE_API_KEY_ENV: &str = "OPENLINEAGE_API_KEY";
19
+
17
20
  #[derive(Clone)]
18
21
  struct ColumnMapping {
19
22
  output_name: String,
@@ -44,9 +47,36 @@ fn resolve_lineage_url(base: &str, endpoint: Option<&str>) -> String {
44
47
  )
45
48
  }
46
49
 
50
+ fn is_unresolved_placeholder(value: &str) -> bool {
51
+ let value = value.trim();
52
+ value.starts_with("{{") && value.ends_with("}}")
53
+ }
54
+
55
+ /// Effective Bearer token for lineage POSTs.
56
+ ///
57
+ /// An explicit `lineage.api_key` wins for backward compatibility. Otherwise Floe falls back
58
+ /// to the ambient `OPENLINEAGE_API_KEY`, mirroring the OpenLineage Python client so a replayed
59
+ /// manifest authenticates from a runner-mounted Secret without the credential ever being
60
+ /// persisted in the manifest. An unexpanded `{{...}}` placeholder is treated as absent:
61
+ /// profile-supplied lineage is merged after config templating (see `apply_profile_lineage`),
62
+ /// so the raw placeholder would otherwise be sent verbatim as the token.
63
+ fn resolve_api_key(configured: Option<&str>, ambient: Option<String>) -> Option<Secret> {
64
+ let explicit = configured
65
+ .map(str::trim)
66
+ .filter(|key| !key.is_empty() && !is_unresolved_placeholder(key));
67
+ match explicit {
68
+ Some(key) => Some(Secret::from(key)),
69
+ None => ambient
70
+ .map(|value| value.trim().to_string())
71
+ .filter(|value| !value.is_empty())
72
+ .map(Secret::from),
73
+ }
74
+ }
75
+
47
76
  pub struct OpenLineageObserver {
48
77
  client: reqwest::blocking::Client,
49
78
  config: LineageConfig,
79
+ api_key: Option<Secret>,
50
80
  entity_start_ms: Mutex<HashMap<String, u128>>,
51
81
  entity_run_ids: Mutex<HashMap<String, String>>,
52
82
  run_start_ms: Mutex<Option<u128>>,
@@ -155,9 +185,15 @@ impl OpenLineageObserver {
155
185
  })
156
186
  .collect();
157
187
 
188
+ let api_key = resolve_api_key(
189
+ config.api_key.as_deref(),
190
+ std::env::var(OPENLINEAGE_API_KEY_ENV).ok(),
191
+ );
192
+
158
193
  Ok(Self {
159
194
  client,
160
195
  config: config.clone(),
196
+ api_key,
161
197
  entity_start_ms: Mutex::new(HashMap::new()),
162
198
  entity_run_ids: Mutex::new(HashMap::new()),
163
199
  run_start_ms: Mutex::new(None),
@@ -172,8 +208,8 @@ impl OpenLineageObserver {
172
208
 
173
209
  fn attempt_post(&self, url: &str, body: &Value) -> Result<(), bool> {
174
210
  let mut req = self.client.post(url).json(body);
175
- if let Some(api_key) = self.config.api_key.as_deref() {
176
- req = req.bearer_auth(api_key);
211
+ if let Some(api_key) = self.api_key.as_ref() {
212
+ req = req.bearer_auth(api_key.expose());
177
213
  }
178
214
  match req.send() {
179
215
  Err(_) => Err(true),
@@ -599,6 +635,23 @@ pub fn build_observer(
599
635
  Ok(Arc::new(obs))
600
636
  }
601
637
 
638
+ /// Build a lineage observer for a `--manifest` replay from the manifest's
639
+ /// embedded lineage block and its entities' resolved cloud datasets, so replay
640
+ /// emits the same input/output lineage as a direct config run. `Ok(None)` when
641
+ /// the manifest has no `lineage` block. See
642
+ /// [`crate::manifest::reconstruct::lineage_inputs_from_manifest_json`].
643
+ pub fn build_observer_from_manifest_json(
644
+ manifest_json: &str,
645
+ config_path: &str,
646
+ ) -> crate::FloeResult<Option<Arc<dyn RunObserver>>> {
647
+ match crate::manifest::reconstruct::lineage_inputs_from_manifest_json(manifest_json)? {
648
+ Some((lineage_cfg, entities)) => {
649
+ build_observer(&lineage_cfg, &entities, config_path).map(Some)
650
+ }
651
+ None => Ok(None),
652
+ }
653
+ }
654
+
602
655
  impl OpenLineageObserver {
603
656
  pub fn is_circuit_open(&self) -> bool {
604
657
  self.circuit_open.load(Ordering::Relaxed)
@@ -608,3 +661,48 @@ impl OpenLineageObserver {
608
661
  self.consecutive_failures.load(Ordering::Relaxed)
609
662
  }
610
663
  }
664
+
665
+ #[cfg(test)]
666
+ mod tests {
667
+ use super::*;
668
+
669
+ fn expose(key: Option<Secret>) -> Option<String> {
670
+ key.map(|s| s.expose().to_string())
671
+ }
672
+
673
+ #[test]
674
+ fn explicit_config_key_wins_over_env() {
675
+ let key = resolve_api_key(Some("cfg-token"), Some("env-token".to_string()));
676
+ assert_eq!(expose(key), Some("cfg-token".to_string()));
677
+ }
678
+
679
+ #[test]
680
+ fn falls_back_to_env_when_config_absent() {
681
+ let key = resolve_api_key(None, Some("env-token".to_string()));
682
+ assert_eq!(expose(key), Some("env-token".to_string()));
683
+ }
684
+
685
+ #[test]
686
+ fn unresolved_placeholder_falls_back_to_env() {
687
+ let key = resolve_api_key(
688
+ Some("{{OPENLINEAGE_API_KEY}}"),
689
+ Some("env-token".to_string()),
690
+ );
691
+ assert_eq!(expose(key), Some("env-token".to_string()));
692
+ }
693
+
694
+ #[test]
695
+ fn unresolved_placeholder_without_env_is_none() {
696
+ assert!(resolve_api_key(Some("{{OPENLINEAGE_API_KEY}}"), None).is_none());
697
+ }
698
+
699
+ #[test]
700
+ fn none_when_neither_source_present() {
701
+ assert!(resolve_api_key(None, None).is_none());
702
+ }
703
+
704
+ #[test]
705
+ fn blank_values_are_ignored() {
706
+ assert!(resolve_api_key(Some(" "), Some(" ".to_string())).is_none());
707
+ }
708
+ }
@@ -56,6 +56,10 @@ pub struct ManifestSinksForRun {
56
56
  pub struct ManifestSinkTargetForRun {
57
57
  pub format: String,
58
58
  pub storage: String,
59
+ /// Resolved cloud URI (e.g. `s3://bucket/key`). Optional so hand-authored
60
+ /// manifests that omit it still parse; the generator always writes it.
61
+ #[serde(default)]
62
+ pub uri: Option<String>,
59
63
  pub path: String,
60
64
  pub options: Option<serde_json::Value>,
61
65
  pub partition_by: Option<Vec<String>>,
@@ -172,6 +176,71 @@ pub fn config_from_manifest_json(json: &str) -> FloeResult<(crate::config::RootC
172
176
  Ok((config, manifest.report_base_uri))
173
177
  }
174
178
 
179
+ /// Reconstruct the lineage config and entities for a `--manifest` replay's
180
+ /// OpenLineage observer, with each source/sink dataset path resolved to the
181
+ /// manifest's cloud `uri`.
182
+ ///
183
+ /// The run reconstruction (`config_from_manifest_json`) intentionally keeps the
184
+ /// raw `path` fields — in the default manifest path mode those hold the original
185
+ /// config paths (e.g. `sales/orders`) while the resolved cloud identity lives in
186
+ /// `uri` (e.g. `s3://bucket/sales/orders`). The observer derives a dataset's
187
+ /// namespace/name from the path, so for lineage it must see the resolved cloud
188
+ /// URI; otherwise a named S3/GCS/ABFS source is reported under the `file`
189
+ /// namespace. Local and relative paths are left as reconstructed.
190
+ ///
191
+ /// Returns `Ok(None)` when the manifest has no `lineage` block.
192
+ pub fn lineage_inputs_from_manifest_json(
193
+ json: &str,
194
+ ) -> FloeResult<Option<(LineageConfig, Vec<EntityConfig>)>> {
195
+ let manifest: ManifestForRun =
196
+ serde_json::from_str(json).map_err(|err| -> Box<dyn std::error::Error + Send + Sync> {
197
+ FloeError::config(format!("manifest parse error: {err}")).into()
198
+ })?;
199
+
200
+ let lineage = match deserialize_manifest_section::<LineageConfig>(
201
+ manifest.lineage.as_ref(),
202
+ "lineage",
203
+ )? {
204
+ Some(lineage) => lineage,
205
+ None => return Ok(None),
206
+ };
207
+
208
+ let mut entities = manifest
209
+ .entities
210
+ .iter()
211
+ .map(entity_from_manifest)
212
+ .collect::<FloeResult<Vec<_>>>()?;
213
+
214
+ // Reconstructed entities are in manifest order, so zip pairs each with its
215
+ // source manifest entry to overlay the resolved cloud URIs.
216
+ for (entity, m) in entities.iter_mut().zip(manifest.entities.iter()) {
217
+ overlay_cloud_uri(&mut entity.source.path, Some(m.source.uri.as_str()));
218
+ overlay_cloud_uri(
219
+ &mut entity.sink.accepted.path,
220
+ m.sinks.accepted.uri.as_deref(),
221
+ );
222
+ if let (Some(rejected), Some(m_rejected)) =
223
+ (entity.sink.rejected.as_mut(), m.sinks.rejected.as_ref())
224
+ {
225
+ overlay_cloud_uri(&mut rejected.path, m_rejected.uri.as_deref());
226
+ }
227
+ }
228
+
229
+ Ok(Some((lineage, entities)))
230
+ }
231
+
232
+ /// Replace `path` with `uri` when `uri` names a cloud object store; other
233
+ /// schemes (local / relative) keep the reconstructed path. The prefix list
234
+ /// mirrors `lineage::split_storage_uri` and must stay in sync with it.
235
+ fn overlay_cloud_uri(path: &mut String, uri: Option<&str>) {
236
+ const CLOUD_SCHEMES: [&str; 6] = ["s3://", "gs://", "gcs://", "az://", "abfss://", "abfs://"];
237
+ if let Some(uri) = uri {
238
+ if CLOUD_SCHEMES.iter().any(|scheme| uri.starts_with(scheme)) {
239
+ *path = uri.to_string();
240
+ }
241
+ }
242
+ }
243
+
175
244
  fn entity_from_manifest(m: &ManifestEntityForRun) -> FloeResult<EntityConfig> {
176
245
  let policy_severity = parse_policy_severity(m.policy_severity.as_deref().unwrap_or("warn"));
177
246
  let write_mode = parse_write_mode(m.write_mode.as_deref().unwrap_or("overwrite"));
@@ -1092,3 +1092,294 @@ fn iceberg_accepted_dataset_namespace_defaults_to_job_namespace() {
1092
1092
  _start_mock.assert();
1093
1093
  _complete_mock.assert();
1094
1094
  }
1095
+
1096
+ // Sole owner of the process-wide OPENLINEAGE_API_KEY variable. The crate has no
1097
+ // serial-test harness, so all three auth paths are asserted inside one test that
1098
+ // controls the variable's lifecycle; no other test reads it, and every other lineage
1099
+ // test either sets an explicit api_key (which wins over the env regardless) or does not
1100
+ // match on request headers, so this cannot race them.
1101
+ #[test]
1102
+ fn api_key_resolution_over_the_wire() {
1103
+ std::env::remove_var("OPENLINEAGE_API_KEY");
1104
+
1105
+ // No config api_key and no env var => request carries no Authorization header.
1106
+ {
1107
+ let mut server = mockito::Server::new();
1108
+ let mock = server
1109
+ .mock("POST", "/api/v1/lineage")
1110
+ .match_header("authorization", mockito::Matcher::Missing)
1111
+ .with_status(200)
1112
+ .expect(1)
1113
+ .create();
1114
+ let config = make_config(&server.url(), None);
1115
+ let obs = OpenLineageObserver::new(&config, &[], "config.yml").unwrap();
1116
+ obs.on_event(run_started_event());
1117
+ mock.assert();
1118
+ }
1119
+
1120
+ // Manifest replay with api_key omitted + env var set => Bearer token from the env.
1121
+ {
1122
+ let mut server = mockito::Server::new();
1123
+ let mock = server
1124
+ .mock("POST", "/api/v1/lineage")
1125
+ .match_header("authorization", "Bearer env-replay-token")
1126
+ .with_status(200)
1127
+ .expect(1)
1128
+ .create();
1129
+ let manifest = format!(
1130
+ r#"{{"spec_version":"0.3","report_base_uri":"file:///tmp","entities":[],"lineage":{{"url":"{}","namespace":"test-ns"}}}}"#,
1131
+ server.url()
1132
+ );
1133
+ std::env::set_var("OPENLINEAGE_API_KEY", "env-replay-token");
1134
+ let (config, _) =
1135
+ floe_core::config_from_manifest_json(&manifest).expect("reconstruct manifest config");
1136
+ let lineage = config.lineage.expect("manifest lineage block");
1137
+ let obs = OpenLineageObserver::new(&lineage, &[], "").unwrap();
1138
+ obs.on_event(run_started_event());
1139
+ mock.assert();
1140
+ }
1141
+
1142
+ // Explicit config api_key wins even when the env var is present (backward compatible).
1143
+ {
1144
+ let mut server = mockito::Server::new();
1145
+ let mock = server
1146
+ .mock("POST", "/api/v1/lineage")
1147
+ .match_header("authorization", "Bearer cfg-token")
1148
+ .with_status(200)
1149
+ .expect(1)
1150
+ .create();
1151
+ let mut config = make_config(&server.url(), None);
1152
+ config.api_key = Some("cfg-token".to_string());
1153
+ let obs = OpenLineageObserver::new(&config, &[], "config.yml").unwrap();
1154
+ obs.on_event(run_started_event());
1155
+ mock.assert();
1156
+ }
1157
+
1158
+ std::env::remove_var("OPENLINEAGE_API_KEY");
1159
+ }
1160
+
1161
+ // ---- Manifest-replay dataset lineage (issue #455) ----
1162
+ // build_observer_from_manifest_json must feed the manifest's entities and their
1163
+ // resolved cloud URIs to the observer so replay emits non-empty inputs/outputs.
1164
+
1165
+ fn replay_entity(name: &str, source_uri: &str, accepted_uri: &str) -> serde_json::Value {
1166
+ json!({
1167
+ "name": name,
1168
+ "source": { "format": "csv", "storage": "bronze", "uri": source_uri, "path": source_uri },
1169
+ "sinks": {
1170
+ "accepted": {
1171
+ "format": "parquet",
1172
+ "storage": "warehouse",
1173
+ "uri": accepted_uri,
1174
+ "path": accepted_uri
1175
+ }
1176
+ },
1177
+ "schema": { "columns": [], "primary_key": [], "unique_keys": [] }
1178
+ })
1179
+ }
1180
+
1181
+ fn replay_manifest(server_url: &str, entities: serde_json::Value) -> String {
1182
+ json!({
1183
+ "spec_version": "0.3",
1184
+ "report_base_uri": "file:///tmp",
1185
+ "entities": entities,
1186
+ "lineage": { "url": server_url, "namespace": "test-ns" }
1187
+ })
1188
+ .to_string()
1189
+ }
1190
+
1191
+ fn entity_started(name: &str) -> RunEvent {
1192
+ RunEvent::EntityStarted {
1193
+ run_id: "test-run-1".to_string(),
1194
+ name: name.to_string(),
1195
+ ts_ms: 1_001_000,
1196
+ }
1197
+ }
1198
+
1199
+ // Replaying a manifest with an entity emits the entity's source input and
1200
+ // accepted output — the regression from issue #455. With entities dropped
1201
+ // (the previous `&[]`), inputs/outputs would be empty and the subset match on
1202
+ // inputs[0]/outputs[0] would fail.
1203
+ #[test]
1204
+ fn manifest_replay_passes_entities_to_observer() {
1205
+ let mut server = mockito::Server::new();
1206
+
1207
+ let start_mock = server
1208
+ .mock("POST", "/api/v1/lineage")
1209
+ .match_body(mockito::Matcher::PartialJson(
1210
+ json!({ "eventType": "START" }),
1211
+ ))
1212
+ .with_status(200)
1213
+ .expect(1)
1214
+ .create();
1215
+
1216
+ let complete_mock = server
1217
+ .mock("POST", "/api/v1/lineage")
1218
+ .match_body(mockito::Matcher::PartialJson(json!({
1219
+ "eventType": "COMPLETE",
1220
+ "inputs": [{
1221
+ "namespace": "s3://lakehouse-bronze",
1222
+ "name": "sales/customer_health/support_tickets"
1223
+ }],
1224
+ "outputs": [{
1225
+ "namespace": "s3://lakehouse-warehouse",
1226
+ "name": "silver/support_tickets"
1227
+ }]
1228
+ })))
1229
+ .with_status(200)
1230
+ .expect(1)
1231
+ .create();
1232
+
1233
+ let manifest = replay_manifest(
1234
+ &server.url(),
1235
+ json!([replay_entity(
1236
+ "support_tickets",
1237
+ "s3://lakehouse-bronze/sales/customer_health/support_tickets",
1238
+ "s3://lakehouse-warehouse/silver/support_tickets",
1239
+ )]),
1240
+ );
1241
+
1242
+ let obs = floe_core::lineage::build_observer_from_manifest_json(&manifest, "")
1243
+ .expect("observer builds from manifest")
1244
+ .expect("manifest has a lineage block => Some observer");
1245
+
1246
+ obs.on_event(entity_started("support_tickets"));
1247
+ obs.on_event(entity_finished_event("support_tickets", "success"));
1248
+
1249
+ start_mock.assert();
1250
+ complete_mock.assert();
1251
+ }
1252
+
1253
+ // Default manifest path mode records raw config paths in `path` and the resolved
1254
+ // cloud identity in `uri`. Lineage must report the cloud dataset from `uri`, not
1255
+ // a `file` namespace derived from the raw path (PR #456 review).
1256
+ #[test]
1257
+ fn manifest_replay_default_path_mode_uses_resolved_cloud_uri() {
1258
+ let mut server = mockito::Server::new();
1259
+
1260
+ let start_mock = server
1261
+ .mock("POST", "/api/v1/lineage")
1262
+ .match_body(mockito::Matcher::PartialJson(
1263
+ json!({ "eventType": "START" }),
1264
+ ))
1265
+ .with_status(200)
1266
+ .expect(1)
1267
+ .create();
1268
+
1269
+ let complete_mock = server
1270
+ .mock("POST", "/api/v1/lineage")
1271
+ .match_body(mockito::Matcher::PartialJson(json!({
1272
+ "eventType": "COMPLETE",
1273
+ "inputs": [{ "namespace": "s3://lakehouse-bronze", "name": "sales/orders" }],
1274
+ "outputs": [{ "namespace": "s3://lakehouse-warehouse", "name": "silver/orders" }]
1275
+ })))
1276
+ .with_status(200)
1277
+ .expect(1)
1278
+ .create();
1279
+
1280
+ // path = raw config paths (default mode); uri = resolved cloud identities.
1281
+ let entity = json!({
1282
+ "name": "orders",
1283
+ "source": {
1284
+ "format": "csv",
1285
+ "storage": "bronze",
1286
+ "uri": "s3://lakehouse-bronze/sales/orders",
1287
+ "path": "sales/orders"
1288
+ },
1289
+ "sinks": {
1290
+ "accepted": {
1291
+ "format": "parquet",
1292
+ "storage": "warehouse",
1293
+ "uri": "s3://lakehouse-warehouse/silver/orders",
1294
+ "path": "silver/orders"
1295
+ }
1296
+ },
1297
+ "schema": { "columns": [], "primary_key": [], "unique_keys": [] }
1298
+ });
1299
+ let manifest = replay_manifest(&server.url(), json!([entity]));
1300
+
1301
+ let obs = floe_core::lineage::build_observer_from_manifest_json(&manifest, "")
1302
+ .expect("observer builds from manifest")
1303
+ .expect("manifest has a lineage block => Some observer");
1304
+
1305
+ obs.on_event(entity_started("orders"));
1306
+ obs.on_event(entity_finished_event("orders", "success"));
1307
+
1308
+ start_mock.assert();
1309
+ complete_mock.assert();
1310
+ }
1311
+
1312
+ // Firing events for only the selected entity (as `--entities orders` does)
1313
+ // still carries that entity's dataset context, because the observer holds all
1314
+ // manifest entities keyed by name.
1315
+ #[test]
1316
+ fn manifest_replay_entity_selection_keeps_dataset_context() {
1317
+ let mut server = mockito::Server::new();
1318
+
1319
+ let start_mock = server
1320
+ .mock("POST", "/api/v1/lineage")
1321
+ .match_body(mockito::Matcher::PartialJson(
1322
+ json!({ "eventType": "START" }),
1323
+ ))
1324
+ .with_status(200)
1325
+ .expect(1)
1326
+ .create();
1327
+
1328
+ let complete_mock = server
1329
+ .mock("POST", "/api/v1/lineage")
1330
+ .match_body(mockito::Matcher::PartialJson(json!({
1331
+ "eventType": "COMPLETE",
1332
+ "inputs": [{ "namespace": "s3://lakehouse-bronze", "name": "sales/orders" }],
1333
+ "outputs": [{ "namespace": "s3://lakehouse-warehouse", "name": "silver/orders" }]
1334
+ })))
1335
+ .with_status(200)
1336
+ .expect(1)
1337
+ .create();
1338
+
1339
+ let manifest = replay_manifest(
1340
+ &server.url(),
1341
+ json!([
1342
+ replay_entity(
1343
+ "orders",
1344
+ "s3://lakehouse-bronze/sales/orders",
1345
+ "s3://lakehouse-warehouse/silver/orders",
1346
+ ),
1347
+ replay_entity(
1348
+ "customers",
1349
+ "s3://lakehouse-bronze/sales/customers",
1350
+ "s3://lakehouse-warehouse/silver/customers",
1351
+ )
1352
+ ]),
1353
+ );
1354
+
1355
+ let obs = floe_core::lineage::build_observer_from_manifest_json(&manifest, "")
1356
+ .expect("observer builds from manifest")
1357
+ .expect("manifest has a lineage block => Some observer");
1358
+
1359
+ // Only "orders" runs; "customers" is not selected.
1360
+ obs.on_event(entity_started("orders"));
1361
+ obs.on_event(entity_finished_event("orders", "success"));
1362
+
1363
+ start_mock.assert();
1364
+ complete_mock.assert();
1365
+ }
1366
+
1367
+ // A manifest without a lineage block yields no observer (lineage disabled),
1368
+ // rather than erroring.
1369
+ #[test]
1370
+ fn manifest_replay_without_lineage_block_yields_no_observer() {
1371
+ let manifest = json!({
1372
+ "spec_version": "0.3",
1373
+ "report_base_uri": "file:///tmp",
1374
+ "entities": []
1375
+ })
1376
+ .to_string();
1377
+
1378
+ let obs = floe_core::lineage::build_observer_from_manifest_json(&manifest, "")
1379
+ .expect("no error when lineage block is absent");
1380
+
1381
+ assert!(
1382
+ obs.is_none(),
1383
+ "manifest without lineage block => no observer"
1384
+ );
1385
+ }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "floe-python"
3
- version = "0.6.9"
3
+ version = "0.6.11"
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.6.9", default-features = false }
17
+ floe-core = { path = "../floe-core", version = "0.6.11", default-features = false }
18
18
  pyo3 = { version = "0.29", features = ["extension-module", "abi3-py310"] }
19
19
  serde_json = "1"
20
20
 
@@ -22,7 +22,7 @@ build-backend = "maturin"
22
22
 
23
23
  [project]
24
24
  name = "floe-duckdb"
25
- version = "0.6.9"
25
+ version = "0.6.11"
26
26
  description = "DuckDB companion extension for floe — adds the native DuckDB sink to the floe Python package"
27
27
  readme = "README.md"
28
28
  requires-python = ">=3.10"
@@ -46,7 +46,7 @@ classifiers = [
46
46
  # only contributes the compiled DuckDB extension module into that package. Pin to
47
47
  # the exact lean version so the companion's `_floe_duckdb` ABI always matches the
48
48
  # `floe` package it plugs into.
49
- dependencies = ["floe-python==0.6.9"]
49
+ dependencies = ["floe-python==0.6.11"]
50
50
 
51
51
  [project.urls]
52
52
  Homepage = "https://github.com/malon64/floe"
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "floe-python"
7
- version = "0.6.9"
7
+ version = "0.6.11"
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"
File without changes
File without changes