floe-python 0.5.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {floe_python-0.5.0 → floe_python-0.5.1}/Cargo.lock +3 -3
- {floe_python-0.5.0 → floe_python-0.5.1}/PKG-INFO +1 -1
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/Cargo.toml +1 -1
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/README.md +5 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/Cargo.toml +2 -2
- floe_python-0.5.1/crates/floe-python/pyproject.duckdb.toml +62 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/src/functions.rs +34 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/src/lib.rs +28 -2
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/src/types/outcome.rs +58 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/pyproject.toml +1 -1
- floe_python-0.5.1/python/floe/__init__.py +222 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/python/floe/_floe.pyi +12 -0
- floe_python-0.5.0/python/floe/__init__.py +0 -76
- {floe_python-0.5.0 → floe_python-0.5.1}/Cargo.toml +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/README.md +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/add_entity.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/checks/cast.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/checks/mismatch.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/checks/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/checks/normalize.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/checks/not_null.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/checks/unique.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/catalog.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/location.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/parse.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/storage.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/template.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/types.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/validate.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/config/yaml_decode.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/errors.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/format.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/avro.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/csv.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/fixed_width.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/json.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/json_selector.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/orc.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/parquet.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/xlsx.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/xml.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/read/xml_selector.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/core/extensions.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/core/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/core/paths.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/core/placement.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/core/planner.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/core/uri.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/core/validation.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/object_store.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/ops/archive.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/ops/inputs.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/ops/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/ops/output.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/providers/adls.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/providers/gcs.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/providers/local.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/providers/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/providers/s3.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/storage/target.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/unique_seed/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/accepted.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/arrow_convert.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/csv.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/delta/commit_metrics.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/delta/options.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/delta/record_batch.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/delta/unity.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/delta/unity_tests.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/delta.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/duckdb/conn.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/duckdb/merge.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/duckdb.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/iceberg/context.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/iceberg/data_files.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/iceberg/glue.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/iceberg/metadata.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/iceberg/rest.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/iceberg/schema.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/iceberg.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/metrics.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/parquet.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/parts.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/sink_format.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/strategy/append.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/strategy/merge/keys.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/strategy/merge/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/strategy/merge/scd1.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/strategy/merge/scd2.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/strategy/merge/shared.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/strategy/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/io/write/strategy/overwrite.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/lib.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/lineage/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/log.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/manifest/builder.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/manifest/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/manifest/model.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/manifest/reconstruct.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/profile/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/profile/parse.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/profile/types.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/profile/validate.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/report/build.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/report/entity.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/report/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/report/output.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/context.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/accepted_buffer.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/accepted_write.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/incremental.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/pii.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/precheck.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/process.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/resolve.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/entity/validate_split.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/events.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/file.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/output.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/run/perf.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/runner/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/runner/outcome.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/runtime.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/state/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/vars/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/vars/resolve.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/src/warnings.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/archive_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/composite_unique.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/delta_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/dry_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/duckdb_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/fixed_width.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/iceberg_gcs_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/iceberg_glue_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/iceberg_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/iceberg_s3_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/json_selectors.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/local_run.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/path_normalization.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration/run_entities_filter.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/integration.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/common.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/add_entity.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/adls_storage.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/adls_validation.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/catalogs.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/config_validation.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/duckdb_validation.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/gcs_storage.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/gcs_validation.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/lineage_validation.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/local_storage.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/parse.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/pii_validation.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/remote_base.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/storage_resolver_uri.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/config/templating.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/format.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/avro_input.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/csv_nulls.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/json_array.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/json_ndjson.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/json_selector.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/orc_input.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/parquet_input.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/tsv.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/xlsx_input.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/xml.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/read/xml_selector.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/adls.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/adls_integration.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/gcs.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/inputs.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/local.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/paths.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/planner.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/s3.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/storage/target.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/delta_merge.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/delta_write.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/iceberg_write.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/metrics.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/object_store.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/parquet_write.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/parts.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/io/write/rejected_csv.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/manifest/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/profile/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/profile/parse.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/profile/validate.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/report/accepted_output.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/report/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/report/storage.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/check_order.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/checks.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/entity/accepted_output.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/entity/incremental.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/entity/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/lineage.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/normalize.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/pii.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/report.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/run/schema_mismatch.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/runner/adapter.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/runner/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/state/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/vars/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit/vars/resolve.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-core/tests/unit.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/.gitignore +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/README.md +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/src/observer.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/src/types/config.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/src/types/errors.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/src/types/mod.rs +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/tests/fixtures/config.yml +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/tests/fixtures/in/customer/customers_valid.csv +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/tests/fixtures/invalid_config.yml +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/tests/fixtures/profile.yml +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/crates/floe-python/tests/test_floe.py +0 -0
- {floe_python-0.5.0 → floe_python-0.5.1}/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.
|
|
3511
|
+
version = "0.5.1"
|
|
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.
|
|
3524
|
+
version = "0.5.1"
|
|
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.
|
|
3568
|
+
version = "0.5.1"
|
|
3569
3569
|
dependencies = [
|
|
3570
3570
|
"floe-core",
|
|
3571
3571
|
"pyo3",
|
|
@@ -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.
|
|
3
|
+
version = "0.5.1"
|
|
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.
|
|
17
|
+
floe-core = { path = "../floe-core", version = "0.5.1", 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.1"
|
|
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.1"]
|
|
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
|
-
|
|
15
|
-
|
|
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))
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|