floe-python 0.8.6__py3-none-any.whl → 0.8.7__py3-none-any.whl
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/__init__.py +2 -0
- floe/checks/normalize.py +3 -1
- floe/checks/rows.py +4 -0
- floe/iceberg/catalog.py +27 -0
- floe/read/csv.py +6 -5
- floe/run/__init__.py +4 -1
- floe/run/entity.py +26 -18
- floe/storage/prefetch.py +46 -0
- floe/storage/resolve.py +18 -2
- {floe_python-0.8.6.dist-info → floe_python-0.8.7.dist-info}/METADATA +1 -1
- {floe_python-0.8.6.dist-info → floe_python-0.8.7.dist-info}/RECORD +14 -13
- {floe_python-0.8.6.dist-info → floe_python-0.8.7.dist-info}/WHEEL +0 -0
- {floe_python-0.8.6.dist-info → floe_python-0.8.7.dist-info}/entry_points.txt +0 -0
- {floe_python-0.8.6.dist-info → floe_python-0.8.7.dist-info}/licenses/LICENSE +0 -0
floe/__init__.py
CHANGED
|
@@ -46,6 +46,7 @@ from floe.run.events import (
|
|
|
46
46
|
)
|
|
47
47
|
from floe.state import dump_state, read_state, reset
|
|
48
48
|
from floe.storage.paths import normalize_local_path
|
|
49
|
+
from floe.storage.resolve import clear_storage_clients
|
|
49
50
|
from floe.storage.types import StorageClient
|
|
50
51
|
|
|
51
52
|
_observer: Callable[[RunEvent], None] | None = None
|
|
@@ -281,6 +282,7 @@ def extract_config_env_vars(config_path: str) -> dict[str, str]:
|
|
|
281
282
|
|
|
282
283
|
|
|
283
284
|
def _state(config_path: str, entity_name: str) -> tuple[str, str, str, StorageClient]:
|
|
285
|
+
clear_storage_clients()
|
|
284
286
|
path, root = _load(config_path)
|
|
285
287
|
[entity] = _run.select_entities(root, [entity_name])
|
|
286
288
|
uri, client = state_uri(entity, normalize_local_path(path.parent), root.storages)
|
floe/checks/normalize.py
CHANGED
|
@@ -115,4 +115,6 @@ def normalize_columns(df: pl.DataFrame, strategy: str | None) -> pl.DataFrame:
|
|
|
115
115
|
f"normalized input column collision: {seen[new]} and {old} -> {new}"
|
|
116
116
|
)
|
|
117
117
|
seen[new] = old
|
|
118
|
-
|
|
118
|
+
renamed = df.clone()
|
|
119
|
+
renamed.columns = list(names.values()) # a rename is a query plan, this is not
|
|
120
|
+
return renamed
|
floe/checks/rows.py
CHANGED
|
@@ -109,6 +109,10 @@ def split_rows(
|
|
|
109
109
|
sources: Mapping[str, str] | None = None,
|
|
110
110
|
) -> tuple[pl.DataFrame, pl.DataFrame]:
|
|
111
111
|
"""`(accepted, rejected)`; rejected adds `__floe_row_index` (UInt64, row position in `df`) and `__floe_errors`."""
|
|
112
|
+
# a null mask passes the row, as in `error_column`
|
|
113
|
+
if not any(c.mask.any() for c in checks):
|
|
114
|
+
none = [pl.Series(ROW_INDEX, [], pl.UInt64), pl.Series(ERRORS, [], pl.String)]
|
|
115
|
+
return df, df.clear().with_columns(none) # replaces a clashing column, as below
|
|
112
116
|
errors = error_column(df.height, checks, formatter, sources)
|
|
113
117
|
rejected = df.with_columns(
|
|
114
118
|
pl.int_range(pl.len(), dtype=pl.UInt64).alias(ROW_INDEX), errors
|
floe/iceberg/catalog.py
CHANGED
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import re
|
|
5
5
|
import shutil
|
|
6
6
|
import tempfile
|
|
7
|
+
import threading
|
|
7
8
|
import weakref
|
|
8
9
|
from collections.abc import Generator, Sequence
|
|
9
10
|
from contextlib import contextmanager
|
|
@@ -83,8 +84,34 @@ def _credential_props(catalog: str, credential: str) -> dict[str, str]:
|
|
|
83
84
|
return {"credential": credential.removeprefix("client_credentials:")}
|
|
84
85
|
|
|
85
86
|
|
|
87
|
+
_CATALOGS: dict[tuple[CatalogDefinition, StorageDefinition | None], Catalog] = {}
|
|
88
|
+
_CATALOGS_LOCK = threading.Lock()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def clear_catalogs() -> None:
|
|
92
|
+
"""Called when a run starts: a catalog is reused within a run only, never across runs (#1101)."""
|
|
93
|
+
with _CATALOGS_LOCK:
|
|
94
|
+
_CATALOGS.clear()
|
|
95
|
+
|
|
96
|
+
|
|
86
97
|
def open_catalog(
|
|
87
98
|
definition: CatalogDefinition, storage: StorageDefinition | None = None
|
|
99
|
+
) -> Catalog:
|
|
100
|
+
"""One catalog per definition and storage until `clear_catalogs`, so the REST init (config, OAuth token) happens once.
|
|
101
|
+
|
|
102
|
+
Safe to call from several threads (one init per key). PyIceberg 0.12 refetches an expired OAuth token
|
|
103
|
+
itself: its REST calls retry once on a 401 after a new client-credentials request; a static `token:` has
|
|
104
|
+
nothing to refetch, as with a freshly opened catalog. A failed init is not kept.
|
|
105
|
+
"""
|
|
106
|
+
key = (definition, storage)
|
|
107
|
+
with _CATALOGS_LOCK:
|
|
108
|
+
if key not in _CATALOGS:
|
|
109
|
+
_CATALOGS[key] = _open_catalog(definition, storage)
|
|
110
|
+
return _CATALOGS[key]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _open_catalog(
|
|
114
|
+
definition: CatalogDefinition, storage: StorageDefinition | None
|
|
88
115
|
) -> Catalog:
|
|
89
116
|
"""Credentials for S3 and Glue come from the environment; the catalog never vends them (as in Rust)."""
|
|
90
117
|
props = {
|
floe/read/csv.py
CHANGED
|
@@ -79,15 +79,16 @@ def _scan_args(options: SourceOptions) -> _Args:
|
|
|
79
79
|
|
|
80
80
|
def _columns(path: Path, args: _Args, declared: Sequence[str]) -> list[str]:
|
|
81
81
|
try:
|
|
82
|
-
probe = args.scan(path, n_rows=0 if args.has_header else 1)
|
|
82
|
+
probe = args.scan(path, n_rows=0 if args.has_header else 1)
|
|
83
|
+
if args.has_header: # the schema is the header: no plan to run
|
|
84
|
+
return probe.collect_schema().names()
|
|
85
|
+
width = probe.collect().width
|
|
83
86
|
except (pl.exceptions.PolarsError, OSError) as err:
|
|
84
87
|
# Rust's polars prints the error kind: `no data: empty CSV`
|
|
85
88
|
kind = "no data: " if isinstance(err, pl.exceptions.NoDataError) else ""
|
|
86
89
|
raise FloeIoError(f"csv header read failed: {kind}{err}") from err
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
names = list(declared[: probe.width])
|
|
90
|
-
return names + [f"extra_column_{i + 1}" for i in range(len(declared), probe.width)]
|
|
90
|
+
names = list(declared[:width])
|
|
91
|
+
return names + [f"extra_column_{i + 1}" for i in range(len(declared), width)]
|
|
91
92
|
|
|
92
93
|
|
|
93
94
|
def _field_counts(path: Path, args: _Args, skip: int) -> Iterator[int]:
|
floe/run/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from floe.checks.rows import Formatter
|
|
|
11
11
|
from floe.config.project import RootConfig
|
|
12
12
|
from floe.config.types import EntityConfig
|
|
13
13
|
from floe.errors import FloeConfigError, FloeError
|
|
14
|
+
from floe.iceberg.catalog import clear_catalogs
|
|
14
15
|
from floe.report.summary import (
|
|
15
16
|
build_summary,
|
|
16
17
|
id_path,
|
|
@@ -24,7 +25,7 @@ from floe.run.entity import EntityRun, list_source, resolve_source, run_entity
|
|
|
24
25
|
from floe.run.events import Emitter, Observer
|
|
25
26
|
from floe.storage.inputs import ResolvedInputs
|
|
26
27
|
from floe.storage.paths import normalize_local_path
|
|
27
|
-
from floe.storage.resolve import resolve_path, storage_client
|
|
28
|
+
from floe.storage.resolve import clear_storage_clients, resolve_path, storage_client
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
@dataclass(frozen=True)
|
|
@@ -104,7 +105,9 @@ def run(
|
|
|
104
105
|
config_uri: str | None = None,
|
|
105
106
|
) -> RunOutcome:
|
|
106
107
|
"""Run the selected entities in config order, writing each entity's report as it finishes. A failing entity raises: no summary, as Rust."""
|
|
108
|
+
clear_storage_clients()
|
|
107
109
|
started, timer = _rfc3339(), time.monotonic()
|
|
110
|
+
clear_catalogs()
|
|
108
111
|
selected = select_entities(root, entities)
|
|
109
112
|
if full_refresh:
|
|
110
113
|
selected = [_refresh(e) for e in selected]
|
floe/run/entity.py
CHANGED
|
@@ -5,7 +5,7 @@ import tempfile
|
|
|
5
5
|
import threading
|
|
6
6
|
import time
|
|
7
7
|
from collections.abc import Callable, Generator, Iterable, Iterator
|
|
8
|
-
from contextlib import suppress
|
|
8
|
+
from contextlib import closing, suppress
|
|
9
9
|
from dataclasses import dataclass, field, replace
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
from functools import partial
|
|
@@ -85,7 +85,6 @@ from floe.storage.inputs import (
|
|
|
85
85
|
InputFile,
|
|
86
86
|
NoInputsError,
|
|
87
87
|
ResolvedInputs,
|
|
88
|
-
localize_input,
|
|
89
88
|
resolve_inputs,
|
|
90
89
|
suffixes_for_format,
|
|
91
90
|
)
|
|
@@ -99,6 +98,7 @@ from floe.storage.partitioning import (
|
|
|
99
98
|
window,
|
|
100
99
|
)
|
|
101
100
|
from floe.storage.paths import normalize_local_path
|
|
101
|
+
from floe.storage.prefetch import localized_inputs
|
|
102
102
|
from floe.storage.resolve import resolve_path, storage_client
|
|
103
103
|
from floe.storage.s3 import S3Client
|
|
104
104
|
from floe.storage.types import StorageClient
|
|
@@ -335,27 +335,31 @@ def _typed(
|
|
|
335
335
|
) -> tuple[pl.DataFrame, list[RowCheck]]:
|
|
336
336
|
entity, src = plan.entity, plan.entity.source
|
|
337
337
|
# D28 (#913): the derived columns join after the file's own columns are normalised and matched
|
|
338
|
-
df = apply_mismatch(normalize_columns(raw, plan.strategy), mismatch)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
df
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
338
|
+
df = apply_mismatch(normalize_columns(raw, plan.strategy), mismatch)
|
|
339
|
+
if derived:
|
|
340
|
+
df = df.with_columns(derived)
|
|
341
|
+
if df.columns != plan.names:
|
|
342
|
+
df = df.select(
|
|
343
|
+
pl.col(n) if n in df.columns else pl.lit(None, pl.String).alias(n)
|
|
344
|
+
for n in plan.names
|
|
345
|
+
)
|
|
345
346
|
columns = entity.schema_.columns
|
|
347
|
+
tag = "_e_cast_" # the mask columns' prefix: no declared column may start with it
|
|
348
|
+
while any(n.startswith(tag) for n in plan.names):
|
|
349
|
+
tag = f"_{tag}"
|
|
346
350
|
try:
|
|
347
351
|
casts = [
|
|
348
352
|
cast_column(n, c.column_type, src.cast_mode, df.schema[n])
|
|
349
353
|
for n, c in zip(plan.names, columns, strict=True)
|
|
350
354
|
]
|
|
351
|
-
|
|
352
|
-
masks =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
+
# one plan for both; unlike select, with_columns broadcasts the constant masks
|
|
356
|
+
masks = [m.alias(f"{tag}{n}") for n, (_, m) in zip(plan.names, casts, strict=True)] # fmt: skip
|
|
357
|
+
both = df.with_columns([t for t, _ in casts] + masks)
|
|
358
|
+
typed = both[plan.names]
|
|
355
359
|
except pl.exceptions.InvalidOperationError as err:
|
|
356
360
|
raise FloeRunError(f"entity.name={entity.name} cast failed: {err}") from err
|
|
357
361
|
cast_checks = [
|
|
358
|
-
RowCheck("cast_error", n, CAST_ERROR_MESSAGE,
|
|
362
|
+
RowCheck("cast_error", n, CAST_ERROR_MESSAGE, both[f"{tag}{n}"])
|
|
359
363
|
for n, c in zip(plan.names, columns, strict=True)
|
|
360
364
|
if c.column_type != "string"
|
|
361
365
|
] # fmt: skip
|
|
@@ -940,14 +944,18 @@ def _run_entity(
|
|
|
940
944
|
def accepted_frames() -> Generator[pl.DataFrame]:
|
|
941
945
|
"""Files are read as the sink pulls (#1056); `files` and `complete` are final once it is exhausted."""
|
|
942
946
|
try:
|
|
943
|
-
|
|
944
|
-
|
|
947
|
+
queue = pending[::-1] if latest_first else pending
|
|
948
|
+
# Rust `localize_input`: a remote input is read from a local copy; #1102: downloaded ahead, and closed before the temp dir goes
|
|
949
|
+
with (
|
|
950
|
+
tempfile.TemporaryDirectory(prefix="floe-") as temp_dir,
|
|
951
|
+
closing(localized_inputs(queue, downloader, Path(temp_dir))) as inputs,
|
|
952
|
+
):
|
|
945
953
|
if pending and sink.write_mode == "append" and plan.tracker.keys and (target is None or plan.table is not None): # fmt: skip
|
|
946
954
|
plan.history = _key_cache(plan, accepted_client, base, Path(temp_dir)) # fmt: skip
|
|
947
|
-
for f in
|
|
955
|
+
for f in queue:
|
|
948
956
|
emit("file_started", entity=entity.name, input=f.source_uri)
|
|
949
957
|
timer = time.monotonic()
|
|
950
|
-
local =
|
|
958
|
+
local = next(inputs)
|
|
951
959
|
report, kept = _run_file(plan, f, local.local_path, Path(temp_dir))
|
|
952
960
|
if local.is_ephemeral:
|
|
953
961
|
local.local_path.unlink()
|
floe/storage/prefetch.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Download the inputs of one entity a few files ahead of the one being read (#1102)."""
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from collections.abc import Generator, Iterable
|
|
5
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from floe.storage.inputs import InputFile, LocalInputFile, localize_input
|
|
9
|
+
from floe.storage.types import Downloader
|
|
10
|
+
|
|
11
|
+
# ponytail: a fixed window of 8 files and 512 MiB (unknown size counts as 0; the next file is always allowed) and one pool per entity; a config knob or a run-wide pool once a bucket needs more
|
|
12
|
+
PREFETCH_WINDOW = 8
|
|
13
|
+
PREFETCH_BYTES = 512 * 1024 * 1024
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def localized_inputs(
|
|
17
|
+
files: Iterable[InputFile], client: Downloader | None, temp_dir: Path
|
|
18
|
+
) -> Generator[LocalInputFile]:
|
|
19
|
+
"""`localize_input` for each file, in order. Close it to stop: pending downloads are cancelled and the running ones awaited."""
|
|
20
|
+
if client is None: # local inputs are used in place; no pool
|
|
21
|
+
for f in files:
|
|
22
|
+
yield localize_input(f, None, temp_dir)
|
|
23
|
+
return
|
|
24
|
+
todo = deque(files)
|
|
25
|
+
ahead: deque[tuple[Future[LocalInputFile], int]] = deque()
|
|
26
|
+
pool = ThreadPoolExecutor(PREFETCH_WINDOW, thread_name_prefix="floe-prefetch")
|
|
27
|
+
|
|
28
|
+
def refill() -> None:
|
|
29
|
+
held = sum(size for _, size in ahead)
|
|
30
|
+
while todo and len(ahead) < PREFETCH_WINDOW:
|
|
31
|
+
size = todo[0].source_size or 0
|
|
32
|
+
if ahead and held + size > PREFETCH_BYTES:
|
|
33
|
+
break
|
|
34
|
+
held += size
|
|
35
|
+
ahead.append(
|
|
36
|
+
(pool.submit(localize_input, todo.popleft(), client, temp_dir), size)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
refill()
|
|
41
|
+
while ahead:
|
|
42
|
+
local = ahead.popleft()[0].result()
|
|
43
|
+
refill()
|
|
44
|
+
yield local
|
|
45
|
+
finally:
|
|
46
|
+
pool.shutdown(cancel_futures=True)
|
floe/storage/resolve.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Storage reference resolution: name, else `storages.default`, else local (Rust `StorageResolver::resolve_path`)."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from threading import Lock
|
|
4
5
|
|
|
5
6
|
from floe.config.storage import StorageDefinition, StoragesConfig
|
|
6
7
|
from floe.errors import FloeConfigError, FloeRunError
|
|
@@ -48,8 +49,23 @@ def resolve_path(
|
|
|
48
49
|
return f"s3://{definition.bucket}/{key}" if key else f"s3://{definition.bucket}", definition # fmt: skip
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
_clients: dict[StorageDefinition, S3Client] = {}
|
|
53
|
+
_clients_lock = Lock()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def clear_storage_clients() -> None:
|
|
57
|
+
"""Start of a run: the clients below read credentials and `AWS_*` env when built."""
|
|
58
|
+
with _clients_lock:
|
|
59
|
+
_clients.clear()
|
|
60
|
+
|
|
61
|
+
|
|
51
62
|
def storage_client(definition: StorageDefinition | None) -> LocalClient | S3Client:
|
|
52
|
-
"""S3 credentials come from the environment, as in Rust."""
|
|
63
|
+
"""S3 credentials come from the environment, as in Rust. One shared client per definition between `clear_storage_clients` calls."""
|
|
53
64
|
if definition is None or definition.type == "local":
|
|
54
65
|
return LocalClient()
|
|
55
|
-
|
|
66
|
+
with (
|
|
67
|
+
_clients_lock
|
|
68
|
+
): # boto3 clients are thread-safe; the lock only stops a duplicate build
|
|
69
|
+
if definition not in _clients:
|
|
70
|
+
_clients[definition] = S3Client(definition.bucket or "", definition.region, definition.endpoint, definition.path_style_access) # fmt: skip
|
|
71
|
+
return _clients[definition]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
floe/__init__.py,sha256=
|
|
1
|
+
floe/__init__.py,sha256=BMJil7rW2sI2OGGcEPUcz_mkZpyKe77sbdjGh7kjPAo,13362
|
|
2
2
|
floe/__main__.py,sha256=Nzz6wrGXRpJiO59qlX7WEJRTlb-XcRFswmP0TA2KjyM,57
|
|
3
3
|
floe/_cli.py,sha256=hwast3uMX0iUtkIpcChSIVHYkoYi4fR4jwCwQT53lXE,14101
|
|
4
4
|
floe/_cli_output.py,sha256=VLxWQr-EPtIwklo9xr2CnjMD17U-7JrMUWdTtAP3jrY,8809
|
|
@@ -13,8 +13,8 @@ floe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
13
13
|
floe/state.py,sha256=XFHF6zVxbdGg1bkONZDLryTD4noDF9IgaimaEI-3Nlg,11104
|
|
14
14
|
floe/checks/cast.py,sha256=lM40xtKEggWI8FW3jijM3T2JFQtaXs8FhsFXo3vy5oM,6788
|
|
15
15
|
floe/checks/mismatch.py,sha256=2Gz-tiWKxy-iehSjAlsGzPLtYeNIfBWvi4yQPlASVYQ,5309
|
|
16
|
-
floe/checks/normalize.py,sha256=
|
|
17
|
-
floe/checks/rows.py,sha256
|
|
16
|
+
floe/checks/normalize.py,sha256=9fdRYFjWGXBAEw1Po71N5tsUWG8eekmSWIQktPduOig,4431
|
|
17
|
+
floe/checks/rows.py,sha256=-yPieSus0-7ZewLv2vR8waHqQ9DGMbVvmJh1_hK4eDs,4433
|
|
18
18
|
floe/checks/unique.py,sha256=qoZVqS_uG_mWZlV7-LdxT6_dMVQRlklEvMutO_V0qaY,12396
|
|
19
19
|
floe/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
floe/config/entity.py,sha256=CLXLXzkUbQhxenD_LwjBt0eoPreaYaxiBVEsdmnFIco,15883
|
|
@@ -27,7 +27,7 @@ floe/config/types.py,sha256=4eveoOlyGmNxyZpI1PSjli9lnXLPc1-jYj4fL8qHN_8,5038
|
|
|
27
27
|
floe/config/validate.py,sha256=7tyofwoTBL-BBCNxnmkXeSOybwmMNngeEcj8zviRyLs,36176
|
|
28
28
|
floe/config/vars.py,sha256=7Bq0IU0KrFeRlyA8W9-F0gDSDKfXCfFpBrX08H8Flvw,4577
|
|
29
29
|
floe/config/yaml.py,sha256=HeFGhKFxYkiqQOwljYOgUD5naJyxL_ZIX6hUIS36tzU,10071
|
|
30
|
-
floe/iceberg/catalog.py,sha256=
|
|
30
|
+
floe/iceberg/catalog.py,sha256=xjRCn6MFZCzleeM9DYkc8f6_In1qEQX_HQpDj1QSFWk,16380
|
|
31
31
|
floe/iceberg/compact.py,sha256=5O372vAIsvgRc2PGihryAsrQ0otn0F30AQu-cetRCP0,7123
|
|
32
32
|
floe/iceberg/merge.py,sha256=VE1Odf2B34Gxj7ZcuhbrfbgTGM9AdiDz6_2BbcQ-kSQ,12448
|
|
33
33
|
floe/iceberg/sink.py,sha256=WyT6yrp5JQyfaqWES96NWh3V-EzgTD4Y-IjB84mZ9Sg,11842
|
|
@@ -37,7 +37,7 @@ floe/lineage/events.py,sha256=4txd8bGUwKBZd-8Aw_ebTurhlxAYk8SVMgH8vSjC3bs,6682
|
|
|
37
37
|
floe/lineage/observer.py,sha256=dDYfWvPpFGjdqvFqQDZ2pTjj7uDfqji2-BTID9hzg90,10075
|
|
38
38
|
floe/read/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
39
|
floe/read/avro.py,sha256=rhGYlrWGFnqt_tyLIph57qV2TH91ULjoJ5qUlpW_6jI,1419
|
|
40
|
-
floe/read/csv.py,sha256=
|
|
40
|
+
floe/read/csv.py,sha256=1lNLh6MGrInWi3TADERA--rOG1iOVcgGdo7JPtt5LdI,5237
|
|
41
41
|
floe/read/json.py,sha256=dPUXNlQUmMeYId3lef8ssEQeHi0tIOoEblvMQDmNVt4,8284
|
|
42
42
|
floe/read/orc.py,sha256=TWgdlLBR2NF_oVZ5zEmRKNXoTkq5QfF2BOFzKyzSjhY,1553
|
|
43
43
|
floe/read/parquet.py,sha256=JwfuWaHj2TxBKJeaKignq8p0Op_pUfsOdVh7dDC3wcE,1491
|
|
@@ -45,8 +45,8 @@ floe/read/xlsx.py,sha256=dengSY8HuNh9ahyR_LR8beqXTZQubqQ1Jx8qEak5jrY,3009
|
|
|
45
45
|
floe/read/xml.py,sha256=7m6DtoU8iExuLROavyojysqOUE_Bd_N0uwFaLAzjI_Y,8004
|
|
46
46
|
floe/report/entity.py,sha256=FRwLtXrlJXDNXrOKscitm-NCNJWQXd0vr_JIf_cUp20,9867
|
|
47
47
|
floe/report/summary.py,sha256=tb547VRkM5WGWHQ-4vZB2DUW4VTWgv9FUoym-NpuWE4,4452
|
|
48
|
-
floe/run/__init__.py,sha256=
|
|
49
|
-
floe/run/entity.py,sha256=
|
|
48
|
+
floe/run/__init__.py,sha256=0QGSqgVTCcEH_Upp7A-Df376aN-pQc4G3F2N6uVebMw,8309
|
|
49
|
+
floe/run/entity.py,sha256=lSQYCAP-d-1O9R44wJF8kgO3U2B60d_PM37NmxLJJfs,49269
|
|
50
50
|
floe/run/events.py,sha256=OE7zYkt0Dz2xrvgNavT1nHfZJ-97IIJ8LpHcameUJZU,2859
|
|
51
51
|
floe/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
52
|
floe/storage/archive.py,sha256=1gPImm9PDSJFCpWjg37ax4ubb0wwc6FZm38quQXhORg,1101
|
|
@@ -54,13 +54,14 @@ floe/storage/inputs.py,sha256=nmgEo2ub3hPUghLUXHU1wnKzpL4aYZbHb6S0XPYvMew,10410
|
|
|
54
54
|
floe/storage/local.py,sha256=UiGFYEY33j45CQspWzRyJBW0HC2Nk9_zG--wL-aUuJI,6388
|
|
55
55
|
floe/storage/partitioning.py,sha256=MG8RJDrwgZhucTswlXC2RwCmzrYIoOE6SH_aBM11KW8,6178
|
|
56
56
|
floe/storage/paths.py,sha256=Z0fry3Gp0Tu2DpUeKqNXdqYP6n_KcKzK2llGx0TGHB8,2343
|
|
57
|
-
floe/storage/
|
|
57
|
+
floe/storage/prefetch.py,sha256=b-_be13xkJIw3LVndOb6SouTd1Pf8uZwG6Wy0Nk1pr8,1801
|
|
58
|
+
floe/storage/resolve.py,sha256=0hGSdU_p10j9vzET25Kcnd-QF6H7uN-hzpo_SC-DV_c,3154
|
|
58
59
|
floe/storage/s3.py,sha256=XcG1qJTEY3KLSMLT950mUdrAxuCA8-Gx3fpNSzyV_Xo,7508
|
|
59
60
|
floe/storage/types.py,sha256=FvAEXWMdVdopRK276JyETRYGOva2-uR8pZvsWj6Xvy0,993
|
|
60
61
|
floe/write/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
62
|
floe/write/outputs.py,sha256=cXxGUIJnZ38XPaqEI6a83GFUsapBRdY52XQm3GlUPcI,6937
|
|
62
|
-
floe_python-0.8.
|
|
63
|
-
floe_python-0.8.
|
|
64
|
-
floe_python-0.8.
|
|
65
|
-
floe_python-0.8.
|
|
66
|
-
floe_python-0.8.
|
|
63
|
+
floe_python-0.8.7.dist-info/METADATA,sha256=xk8WIhLuQqvT4lPVlzSISa5FRfsyhIpL3TjcfVUGGoo,442
|
|
64
|
+
floe_python-0.8.7.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
65
|
+
floe_python-0.8.7.dist-info/entry_points.txt,sha256=DU75r-E3zcK0quvwg5iT5DeWbUJck_Tz80bEj1IIzHA,40
|
|
66
|
+
floe_python-0.8.7.dist-info/licenses/LICENSE,sha256=dUhuoK-TCRQMpuLEAdfme-qPSJI0TlcH9jlNxeg9_EQ,1056
|
|
67
|
+
floe_python-0.8.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|