ikichunk 0.2.0__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.
- ikichunk-0.2.0/LICENSE +21 -0
- ikichunk-0.2.0/PKG-INFO +615 -0
- ikichunk-0.2.0/README.MD +574 -0
- ikichunk-0.2.0/pyproject.toml +53 -0
- ikichunk-0.2.0/setup.cfg +4 -0
- ikichunk-0.2.0/src/ikichunk/__init__.py +8 -0
- ikichunk-0.2.0/src/ikichunk/automation/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/automation/tasks.py +5 -0
- ikichunk-0.2.0/src/ikichunk/cli.py +41 -0
- ikichunk-0.2.0/src/ikichunk/concurrency/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/concurrency/parallel.py +43 -0
- ikichunk-0.2.0/src/ikichunk/configuration/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/configuration/loader.py +43 -0
- ikichunk-0.2.0/src/ikichunk/exceptions.py +25 -0
- ikichunk-0.2.0/src/ikichunk/facade.py +167 -0
- ikichunk-0.2.0/src/ikichunk/inspection/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/inspection/inspectors.py +59 -0
- ikichunk-0.2.0/src/ikichunk/integrity/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/integrity/hashing.py +21 -0
- ikichunk-0.2.0/src/ikichunk/io/__init__.py +6 -0
- ikichunk-0.2.0/src/ikichunk/io/atomic.py +20 -0
- ikichunk-0.2.0/src/ikichunk/io/core.py +56 -0
- ikichunk-0.2.0/src/ikichunk/io/formats.py +252 -0
- ikichunk-0.2.0/src/ikichunk/io/streaming.py +27 -0
- ikichunk-0.2.0/src/ikichunk/net/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/net/http.py +8 -0
- ikichunk-0.2.0/src/ikichunk/observability/__init__.py +4 -0
- ikichunk-0.2.0/src/ikichunk/observability/logging_.py +18 -0
- ikichunk-0.2.0/src/ikichunk/observability/timing.py +32 -0
- ikichunk-0.2.0/src/ikichunk/partitioning/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/partitioning/strategies.py +177 -0
- ikichunk-0.2.0/src/ikichunk/plugins/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/plugins/registry.py +5 -0
- ikichunk-0.2.0/src/ikichunk/resilience/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/resilience/retry.py +21 -0
- ikichunk-0.2.0/src/ikichunk/storage/__init__.py +5 -0
- ikichunk-0.2.0/src/ikichunk/storage/codecs.py +19 -0
- ikichunk-0.2.0/src/ikichunk/storage/compression.py +152 -0
- ikichunk-0.2.0/src/ikichunk/system/__init__.py +4 -0
- ikichunk-0.2.0/src/ikichunk/system/platform.py +98 -0
- ikichunk-0.2.0/src/ikichunk/templates/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/templates/rendering.py +31 -0
- ikichunk-0.2.0/src/ikichunk/validation/__init__.py +3 -0
- ikichunk-0.2.0/src/ikichunk/validation/validate.py +20 -0
- ikichunk-0.2.0/src/ikichunk.egg-info/PKG-INFO +615 -0
- ikichunk-0.2.0/src/ikichunk.egg-info/SOURCES.txt +49 -0
- ikichunk-0.2.0/src/ikichunk.egg-info/dependency_links.txt +1 -0
- ikichunk-0.2.0/src/ikichunk.egg-info/entry_points.txt +2 -0
- ikichunk-0.2.0/src/ikichunk.egg-info/requires.txt +16 -0
- ikichunk-0.2.0/src/ikichunk.egg-info/top_level.txt +1 -0
- ikichunk-0.2.0/tests/test_regression.py +241 -0
ikichunk-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Franz Monzales
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ikichunk-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ikichunk
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Enterprise-portable Swiss-army-knife scripting toolkit for data engineering and systems automation.
|
|
5
|
+
Author: ikidevz
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/ikichunk
|
|
8
|
+
Project-URL: Repository, https://github.com/yourusername/ikichunk.git
|
|
9
|
+
Project-URL: Documentation, https://github.com/yourusername/ikichunk
|
|
10
|
+
Project-URL: Issues, https://github.com/yourusername/ikichunk/issues
|
|
11
|
+
Keywords: data-engineering,partitioning,streaming,compression,parallel-processing,scripting
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Provides-Extra: yaml
|
|
29
|
+
Requires-Dist: PyYAML>=6.0; extra == "yaml"
|
|
30
|
+
Provides-Extra: parquet
|
|
31
|
+
Requires-Dist: pyarrow>=14.0; extra == "parquet"
|
|
32
|
+
Provides-Extra: zstd
|
|
33
|
+
Requires-Dist: zstandard>=0.22; extra == "zstd"
|
|
34
|
+
Provides-Extra: full
|
|
35
|
+
Requires-Dist: PyYAML>=6.0; extra == "full"
|
|
36
|
+
Requires-Dist: pyarrow>=14.0; extra == "full"
|
|
37
|
+
Requires-Dist: rich>=13.0; extra == "full"
|
|
38
|
+
Requires-Dist: tqdm>=4.0; extra == "full"
|
|
39
|
+
Requires-Dist: zstandard>=0.22; extra == "full"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# IKiChunk
|
|
43
|
+
|
|
44
|
+
**Enterprise-portable Swiss-army-knife scripting toolkit** for Data Engineers, Data Architects, Data Scientists, Software Engineers, and Systems Engineers.
|
|
45
|
+
|
|
46
|
+
One facade, zero required dependencies—tackle common data engineering tasks with minimal code.
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from ikichunk import partition # the only import most users need
|
|
52
|
+
|
|
53
|
+
parts = partition.smart_split("big.csv", goal="parallel")
|
|
54
|
+
results = partition.pmap(process, parts, workers=len(parts))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Every method documented below is real, production-tested code—not a spec. See `IKiChunk-Codebase-v2.md` for the full source and `IKiChunk-Examples.md` for worked examples against a 2,000,000-row dataset.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Table of Contents
|
|
62
|
+
|
|
63
|
+
1. [Installation](#installation)
|
|
64
|
+
2. [Quickstart](#quickstart)
|
|
65
|
+
3. [Core Concepts](#core-concepts)
|
|
66
|
+
4. [Feature Reference](#feature-reference)
|
|
67
|
+
- [I/O](#io)
|
|
68
|
+
- [Inspect](#inspect)
|
|
69
|
+
- [Config & Secrets](#config--secrets)
|
|
70
|
+
- [Logging](#logging)
|
|
71
|
+
- [Time](#time)
|
|
72
|
+
- [Retry](#retry)
|
|
73
|
+
- [Parallel (pmap)](#parallel-pmap)
|
|
74
|
+
- [Partition & Chunk](#partition--chunk-the-namesake-feature)
|
|
75
|
+
- [Hash & Integrity](#hash--integrity)
|
|
76
|
+
- [Compress](#compress)
|
|
77
|
+
- [Archive](#archive)
|
|
78
|
+
- [Platform & Portability](#platform--portability)
|
|
79
|
+
- [Process](#process)
|
|
80
|
+
- [Net](#net)
|
|
81
|
+
- [Watch](#watch)
|
|
82
|
+
- [Template](#template)
|
|
83
|
+
- [Validate](#validate)
|
|
84
|
+
5. [Extensibility](#extensibility)
|
|
85
|
+
6. [CLI](#cli)
|
|
86
|
+
7. [Known Limitations](#known-limitations)
|
|
87
|
+
8. [Project Structure](#project-structure)
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# from the project root (contains pyproject.toml and src/)
|
|
95
|
+
pip install -e .
|
|
96
|
+
|
|
97
|
+
# with optional format/codec support
|
|
98
|
+
pip install -e ".[yaml]" # PyYAML for .yaml/.yml
|
|
99
|
+
pip install -e ".[parquet]" # pyarrow for .parquet
|
|
100
|
+
pip install -e ".[zstd]" # zstandard for algo="zstd" compression
|
|
101
|
+
pip install -e ".[full]" # all of the above + rich/tqdm for nicer CLI output
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Requires Python ≥3.9. The core package has **zero required dependencies**—everything below works out of the box on a bare Python install.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Quickstart
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from ikichunk import partition
|
|
112
|
+
|
|
113
|
+
# 1. Inspect unknown data — safe on config, files, DataFrames, lists, dicts
|
|
114
|
+
print(partition.inspect("data.csv"))
|
|
115
|
+
|
|
116
|
+
# 2. Safe, atomic write with a backup of the previous version
|
|
117
|
+
partition.write("out.json", {"status": "ok"}, backup=True)
|
|
118
|
+
|
|
119
|
+
# 3. Split a big file without loading it into memory
|
|
120
|
+
parts = partition.split_file("big.csv", by="rows", rows=100_000)
|
|
121
|
+
|
|
122
|
+
# 4. Or let it decide the split shape for you
|
|
123
|
+
parts = partition.smart_split("big.csv", goal="parallel")
|
|
124
|
+
|
|
125
|
+
# 5. Process each partition in parallel
|
|
126
|
+
results = partition.pmap(my_transform, parts, workers=len(parts))
|
|
127
|
+
|
|
128
|
+
# 6. Verify nothing got corrupted along the way
|
|
129
|
+
m = partition.manifest(parts)
|
|
130
|
+
ok = all(partition.verify(f["path"], f["hash"]) for f in m["files"])
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Core Concepts
|
|
136
|
+
|
|
137
|
+
- **The facade.** `partition` is a ready-to-use singleton instance of the `Partition` class. Every feature is reachable as `partition.<method>`—you never import a submodule directly for normal use.
|
|
138
|
+
- **No silent fallback.** Ambiguous formats, unpicklable process-pool arguments, and shell-metacharacter strings raise clearly instead of silently guessing.
|
|
139
|
+
- **Streaming-first.** Anything that touches "big" data (`stream`, `split_file`, `hash`, `download`) is designed to avoid loading entire files into memory.
|
|
140
|
+
- **Instantiable, not just a singleton.** Need an isolated instance (e.g., for tests)?
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from ikichunk import Partition
|
|
144
|
+
|
|
145
|
+
test_partition = Partition(log_level="DEBUG", env_prefix="TEST_")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Feature Reference
|
|
151
|
+
|
|
152
|
+
### I/O
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
partition.read(path, fmt=None, **kwargs) -> Any
|
|
156
|
+
partition.write(path, data, *, atomic=True, backup=False, fmt=None, **kwargs) -> str
|
|
157
|
+
partition.exists(path) -> bool
|
|
158
|
+
partition.ensure_dir(path) -> str
|
|
159
|
+
partition.list_files(path=".", pattern="*", recursive=False) -> list[str]
|
|
160
|
+
partition.ls(...) # alias for list_files
|
|
161
|
+
partition.cat(path) -> str # alias for read(path, fmt="text")
|
|
162
|
+
partition.stream(path, fmt=None, chunk_size=None) -> Iterator[Any]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Supported formats: `json`, `yaml` _(extra)_, `csv`, `tsv`, `parquet` _(extra)_, `pickle`, `text`—auto-detected from the file extension or passed explicitly via `fmt=`.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
partition.write("report.json", {"rows": 42}, atomic=True, backup=True)
|
|
169
|
+
data = partition.read("report.json")
|
|
170
|
+
|
|
171
|
+
# Streaming: constant memory regardless of file size
|
|
172
|
+
for row in partition.stream("huge.csv"):
|
|
173
|
+
process(row)
|
|
174
|
+
|
|
175
|
+
# Unknown extensions raise instead of silently guessing "text"
|
|
176
|
+
partition.read("data.xyz")
|
|
177
|
+
# → UnknownFormatError: Cannot determine format for 'data.xyz' ...
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Extending formats at runtime:**
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from ikichunk.io.formats import FormatHandler
|
|
184
|
+
|
|
185
|
+
def read_upper(path, kwargs): return path.read_text().upper()
|
|
186
|
+
def write_upper(path, data, kwargs): path.write_text(str(data).upper())
|
|
187
|
+
|
|
188
|
+
partition.register_format("shout", FormatHandler("shout", read_upper, write_upper), extensions=[".shout"])
|
|
189
|
+
partition.write("out.shout", "hello")
|
|
190
|
+
partition.read("out.shout") # "HELLO"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### Inspect
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
partition.inspect(obj_or_path, sample=3, **kwargs) -> dict
|
|
199
|
+
partition.head(obj_or_path, n=5, **kwargs) -> Any
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Works on file paths, lists, dicts, strings, and pandas DataFrames (if installed). Automatically redacts values whose keys look like secrets (`*_key`, `*_token`, `*_secret`, `*_password`, etc.):
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
partition.inspect({"user": "alice", "api_key": "sk-live-..."})
|
|
206
|
+
# {'type': 'dict', 'keys': ['user', 'api_key'],
|
|
207
|
+
# 'sample': {'user': 'alice', 'api_key': '<redacted>'}}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`sample=` controls how many items are included—set it high enough (`sample=len(obj)`) to see all keys, not just the first few.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### Config & Secrets
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
partition.config(*sources, secrets=None, env_prefix=None, **kwargs) -> dict
|
|
218
|
+
partition.env(key, default=None, cast=str) -> Any
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Merge order (later values override earlier ones): files → secrets → environment variables.
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
cfg = partition.config("config.yaml", secrets=".env", env_prefix="APP_")
|
|
225
|
+
db_host = partition.env("DB_HOST", default="localhost")
|
|
226
|
+
port = partition.env("PORT", default=8080, cast=int)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Secrets loaded via `secrets=` are flagged for redaction whenever the config dict passes through `inspect()`. They are never cached to disk beyond the source file.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### Logging
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
partition.log(name=None, level=None, **kwargs) -> logging.Logger
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Zero-config, console-friendly, structured output.
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
log = partition.log("etl", level="DEBUG")
|
|
243
|
+
log.info("processed %d rows", 1000)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### Time
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
partition.now(fmt="%Y-%m-%d %H:%M:%S") -> str
|
|
252
|
+
partition.timer(name="block") # context manager
|
|
253
|
+
partition.duration(seconds) -> str # human-readable
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
with partition.timer("load"):
|
|
258
|
+
df = partition.read("big.csv")
|
|
259
|
+
# prints: [load] 3s
|
|
260
|
+
|
|
261
|
+
print(partition.duration(3725)) # "1h 2m 5s"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### Retry
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
@partition.retry(tries=3, delay=1.0, backoff=2.0, exceptions=(Exception,))
|
|
270
|
+
def flaky_call(): ...
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Exponential backoff decorator that composes cleanly with `net.fetch` for retry-on-failure HTTP calls:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
@partition.retry(tries=3, delay=1.0, exceptions=(TimeoutError,))
|
|
277
|
+
def fetch_with_retry(url):
|
|
278
|
+
return partition.fetch(url, timeout=5)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
### Parallel (pmap)
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
partition.pmap(func, items, *, workers=None, backend="thread"|"process",
|
|
287
|
+
retries=0, progress=False, ordered=True) -> list
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
results = partition.pmap(transform, records, workers=8, retries=2, progress=True)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
- `backend="thread"` (default)—no picklability constraint, best for I/O-bound work.
|
|
295
|
+
- `backend="process"`—for CPU-bound work. Both `func` and every item must be picklable (no lambdas or closures). A clear `PartitionParallelError` is raised at call time if they aren't, rather than a raw multiprocessing traceback.
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
def region_revenue(part_path): # must be a top-level function for backend="process"
|
|
299
|
+
...
|
|
300
|
+
|
|
301
|
+
partition.pmap(region_revenue, parts, workers=4, backend="process")
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### Partition & Chunk _(the namesake feature)_
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
partition.split_file(path, *, by="size"|"rows"|"count", size=None, rows=None,
|
|
310
|
+
count=None, out_dir=None, fmt=None, prefix=None) -> list[str]
|
|
311
|
+
partition.smart_split(path, *, goal="parallel"|"memory-safe"|"storage",
|
|
312
|
+
workers=None, out_dir=None, fmt=None, explain=False) -> list[str]
|
|
313
|
+
partition.chunks(iterable, size) -> Iterator[list]
|
|
314
|
+
partition.manifest(paths, *, hash_algo="sha256") -> dict
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Manual splitting**—three strategies, all streaming (never load whole source files into memory):
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
partition.split_file("big.csv", by="size", size="256MB", out_dir="parts/")
|
|
321
|
+
partition.split_file("big.csv", by="rows", rows=100_000, out_dir="parts/")
|
|
322
|
+
partition.split_file("big.csv", by="count", count=8, out_dir="parts/") # exact even split
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`by="size"` is a raw byte-level split—fast and works on any file, but partitions may cut mid-row for row-based formats. `by="rows"`/`by="count"` are row-aware for `csv`/`tsv` and repeat the header in every partition by default.
|
|
326
|
+
|
|
327
|
+
**Auto-strategy splitting**—inspects the file and system, picks an optimal shape for you:
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
parts = partition.smart_split("big.csv", goal="parallel") # count = CPU count
|
|
331
|
+
parts = partition.smart_split("big.csv", goal="memory-safe") # size capped under available RAM
|
|
332
|
+
parts = partition.smart_split("big.csv", goal="storage") # 100MB parts, object-storage sized
|
|
333
|
+
|
|
334
|
+
# See the reasoning, not just the result:
|
|
335
|
+
parts, why = partition.smart_split("big.csv", goal="parallel", explain=True)
|
|
336
|
+
print(why) # {'goal': 'parallel', 'cpu_count': 8, 'chosen_strategy': {...}, 'partition_count': 8, ...}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Generic in-memory chunking**—feeds `pmap`:
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
for batch in partition.chunks(records, size=500):
|
|
343
|
+
partition.pmap(transform, batch, workers=8)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Manifests**—an auditable record of a partition batch:
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
m = partition.manifest(parts)
|
|
350
|
+
# {'algo': 'sha256', 'files': [{'path': ..., 'size_bytes': ..., 'hash': ...}, ...]}
|
|
351
|
+
partition.write("manifest.json", m)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**Extending goals at runtime:**
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
def goal_tiny(file_size, info, workers):
|
|
358
|
+
return {"by": "size", "size": 1024 * 1024} # force 1MB partitions
|
|
359
|
+
|
|
360
|
+
partition.register_split_goal("tiny", goal_tiny)
|
|
361
|
+
partition.smart_split("big.csv", goal="tiny")
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
### Hash & Integrity
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
partition.hash(path_or_bytes, algo="sha256") -> str
|
|
370
|
+
partition.verify(path, expected_hash, algo="sha256") -> bool
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Streams the file in fixed-size blocks—no full-file memory load.
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
h = partition.hash("part1.csv")
|
|
377
|
+
assert partition.verify("part1.csv", h) # True — unless the file changed
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Typical use: verify a `manifest()` after copying or transferring partitions to another location.
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
manifest = partition.read("manifest.json")
|
|
384
|
+
for entry in manifest["files"]:
|
|
385
|
+
if not partition.verify(entry["path"], entry["hash"]):
|
|
386
|
+
raise RuntimeError(f"corrupted: {entry['path']}")
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
### Compress
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
partition.compress(path, *, algo="gzip"|"zip"|"zstd", out=None, keep_original=True) -> str
|
|
395
|
+
partition.decompress(path, *, out=None) -> str
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
`gzip` and `zip` are in the standard library. `algo="zstd"` requires `pip install ikichunk[zstd]`—attempting it without the extra raises a `MissingDependencyError` with the exact install command.
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
gz_path = partition.compress("part1.csv", algo="gzip")
|
|
402
|
+
restored = partition.decompress(gz_path, out="restored.csv")
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Extending codecs at runtime** via `partition.register_codec(name, codec)` with a `storage.codecs.Codec(name, default_ext, compress_fn, decompress_fn)`.
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
### Archive
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
partition.archive(source, out_path, *, fmt="tar.gz"|"zip") -> str
|
|
413
|
+
partition.extract(archive_path, *, out_dir=None) -> str
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Distinct from `compress()`—this bundles a **directory** (or multiple files) into a single archive, rather than shrinking one file.
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
partition.archive("parts/", "parts_batch.tar.gz")
|
|
420
|
+
partition.extract("parts_batch.tar.gz", out_dir="restored/")
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
`extract()` refuses to write outside `out_dir` (zip-slip guard)—this is a hard rule, not configurable.
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### Platform & Portability
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
partition.platform_info() -> dict
|
|
431
|
+
partition.which(cmd) -> str | None
|
|
432
|
+
partition.normalize_path(path) -> str
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
info = partition.platform_info()
|
|
437
|
+
# {'os': 'Linux', 'cpu_count': 8, 'available_memory_bytes': ..., 'ikichunk_version': '0.2.0', ...}
|
|
438
|
+
|
|
439
|
+
if partition.which("docker") is None:
|
|
440
|
+
raise RuntimeError("docker not found on PATH")
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
### Process
|
|
446
|
+
|
|
447
|
+
```python
|
|
448
|
+
partition.is_running(pid) -> bool
|
|
449
|
+
partition.kill(pid, *, timeout=5, force=False) -> bool
|
|
450
|
+
partition.wait_for_port(host, port, *, timeout=30, interval=0.5) -> bool
|
|
451
|
+
partition.is_port_open(host, port, *, timeout=1) -> bool
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Checks and signals processes—**not** a supervisor. It never restarts or daemonizes.
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
partition.run(["systemctl", "restart", "myapp"])
|
|
458
|
+
if partition.wait_for_port("localhost", 8080, timeout=30):
|
|
459
|
+
log.info("service is up")
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
`kill()` sends `SIGTERM` first, polls up to `timeout` seconds, and only sends `SIGKILL` if `force=True` and the process is still alive.
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
### Net
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
partition.fetch(url, *, timeout=10, headers=None) -> bytes | str
|
|
470
|
+
partition.download(url, path, *, timeout=30, progress=False) -> str
|
|
471
|
+
partition.reachable(host, port=None, *, timeout=2) -> bool
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Built on stdlib `urllib`—**not** a `requests` replacement (no sessions, auth flows, or pagination). Covers the "I just need to fetch one thing" case.
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
if partition.reachable("api.example.com", 443):
|
|
478
|
+
data = partition.fetch("https://api.example.com/status")
|
|
479
|
+
|
|
480
|
+
partition.download("https://example.com/dataset.csv", "dataset.csv", progress=True)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
`download()` streams directly to a temp file and atomically renames into place—a failed download never leaves a corrupted partial file.
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
### Watch
|
|
488
|
+
|
|
489
|
+
```python
|
|
490
|
+
partition.watch(path, *, on_change=None, interval=1.0, recursive=False) -> WatchHandle
|
|
491
|
+
partition.watch_once(path, *, since=None) -> bool
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Poll-based (mtime + size), not OS-native filesystem events—portable across every OS with zero dependencies.
|
|
495
|
+
|
|
496
|
+
```python
|
|
497
|
+
handle = partition.watch("config/", on_change=lambda p: reload_config(), interval=1.0)
|
|
498
|
+
# ... later
|
|
499
|
+
handle.stop()
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
### Template
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
partition.render(template, variables, *, out=None, strict=True) -> str
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Variable substitution only (stdlib `string.Template`, `$var` syntax)—**not** a templating engine. No loops or conditionals.
|
|
511
|
+
|
|
512
|
+
```python
|
|
513
|
+
partition.render("app.env.tmpl", {"DB_HOST": partition.env("DB_HOST")}, out="app.env")
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
`strict=True` (default) raises on a missing variable instead of silently leaving it unrendered. Pass `strict=False` to allow partial renders.
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
### Validate
|
|
521
|
+
|
|
522
|
+
```python
|
|
523
|
+
partition.require(condition, msg="Requirement failed")
|
|
524
|
+
partition.not_none(value, name="value") -> value
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
partition.require(len(records) > 0, "no records loaded")
|
|
529
|
+
config_val = partition.not_none(cfg.get("api_key"), name="api_key")
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## Extensibility
|
|
535
|
+
|
|
536
|
+
Every registry-backed feature above (`register_format`, `register_codec`, `register_split_goal`, `register_inspector`) adds new capabilities without editing the library source. The facade has one more general mechanism:
|
|
537
|
+
|
|
538
|
+
```python
|
|
539
|
+
def double(x):
|
|
540
|
+
return x * 2
|
|
541
|
+
|
|
542
|
+
partition.register("double", double)
|
|
543
|
+
partition.double(21) # 42
|
|
544
|
+
|
|
545
|
+
# Refuses to silently clobber an existing method:
|
|
546
|
+
partition.register("read", lambda x: x)
|
|
547
|
+
# ValueError: 'read' is already a facade method/attribute
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Independent instances** for testing or multi-configuration use:
|
|
551
|
+
|
|
552
|
+
```python
|
|
553
|
+
from ikichunk import Partition
|
|
554
|
+
|
|
555
|
+
worker_partition = Partition(log_level="DEBUG", env_prefix="WORKER_")
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Pip-installable plugins** register automatically via an `ikichunk.plugins` setuptools entry point, discovered at import time—no manual `register()` call needed for third-party packages.
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## CLI
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
ikichunk inspect data.csv
|
|
566
|
+
ikichunk ls . -r -p "*.json"
|
|
567
|
+
ikichunk split big.csv --by size --size 256MB --out-dir parts/
|
|
568
|
+
ikichunk smart-split big.csv --goal parallel --out-dir parts/
|
|
569
|
+
ikichunk wait-for-port localhost 8080 --timeout 30
|
|
570
|
+
ikichunk archive parts/ parts.tar.gz
|
|
571
|
+
ikichunk platform
|
|
572
|
+
ikichunk version
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Every subcommand maps 1:1 to a facade method—the CLI carries no logic the Python API doesn't also expose. Run `ikichunk --help` for the full list of available subcommands.
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Known Limitations
|
|
580
|
+
|
|
581
|
+
- `split_file(by="count")` on row-based formats (CSV/TSV) does two streaming passes—at most one partition's worth of rows is held in memory at a time, not the whole file. `by="rows"`/`by="size"` are single-pass throughout.
|
|
582
|
+
- `net` and `template` are deliberately minimal—reach for `requests`/`httpx` or `Jinja2` if you need sessions, auth flows, or templating logic.
|
|
583
|
+
- `watch()` is poll-based, not OS-native events—suitable for config-reload use cases, not designed for high-frequency, low-latency file watching at scale.
|
|
584
|
+
- `smart_split(goal="memory-safe")` uses a heuristic safety margin (10% of available RAM, 64MB floor), not a guaranteed bound—tune it via `register_split_goal` if your workload needs stricter guarantees.
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## Project Structure
|
|
589
|
+
|
|
590
|
+
```
|
|
591
|
+
ikichunk/
|
|
592
|
+
├── pyproject.toml
|
|
593
|
+
└── src/
|
|
594
|
+
└── ikichunk/
|
|
595
|
+
├── __init__.py # exports `partition` (singleton) and `Partition` (class)
|
|
596
|
+
├── facade.py # Partition class — the single public entry point
|
|
597
|
+
├── exceptions.py # centralized custom exceptions
|
|
598
|
+
├── io/ # read/write/stream — Strategy-pattern format registry
|
|
599
|
+
├── inspection/ # inspect/head — type-dispatch registry
|
|
600
|
+
├── configuration/ # config/env
|
|
601
|
+
├── observability/ # log/now/timer/duration
|
|
602
|
+
├── resilience/ # retry
|
|
603
|
+
├── concurrency/ # pmap
|
|
604
|
+
├── partitioning/ # split_file/smart_split/chunks/manifest — the namesake package
|
|
605
|
+
├── integrity/ # hash/verify
|
|
606
|
+
├── storage/ # compress/decompress/archive/extract — Strategy-pattern codecs
|
|
607
|
+
├── system/ # platform_info/process/shell
|
|
608
|
+
├── net/ # fetch/download/reachable
|
|
609
|
+
├── automation/ # watch/render
|
|
610
|
+
├── validation/ # require/not_none
|
|
611
|
+
├── plugins/ # entry-point plugin discovery
|
|
612
|
+
└── cli/ # Command-pattern CLI, thin wrapper over the facade
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
See `IKiChunk-Blueprint.md` for design rationale, `IKiChunk-Codebase-v2.md` for the full source, and `IKiChunk-Examples.md` for executed examples with output captured against a real 2,000,000-row dataset.
|