archive-r-python 0.1.36__cp315-cp315-win_amd64.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.
Files changed (30) hide show
  1. archive_r.cp315-win_amd64.pyd +0 -0
  2. archive_r_python-0.1.36.data/data/.libs/archive.dll +0 -0
  3. archive_r_python-0.1.36.data/data/.libs/archive_r_core.dll +0 -0
  4. archive_r_python-0.1.36.data/data/.libs/bz2.dll +0 -0
  5. archive_r_python-0.1.36.data/data/.libs/legacy.dll +0 -0
  6. archive_r_python-0.1.36.data/data/.libs/libcrypto-3-x64.dll +0 -0
  7. archive_r_python-0.1.36.data/data/.libs/liblzma.dll +0 -0
  8. archive_r_python-0.1.36.data/data/.libs/libssl-3-x64.dll +0 -0
  9. archive_r_python-0.1.36.data/data/.libs/lz4.dll +0 -0
  10. archive_r_python-0.1.36.data/data/.libs/z.dll +0 -0
  11. archive_r_python-0.1.36.data/data/.libs/zstd.dll +0 -0
  12. archive_r_python-0.1.36.dist-info/METADATA +633 -0
  13. archive_r_python-0.1.36.dist-info/RECORD +30 -0
  14. archive_r_python-0.1.36.dist-info/WHEEL +5 -0
  15. archive_r_python-0.1.36.dist-info/licenses/LICENSE +21 -0
  16. archive_r_python-0.1.36.dist-info/licenses/LICENSES/acl-COPYING.LGPL +513 -0
  17. archive_r_python-0.1.36.dist-info/licenses/LICENSES/attr-COPYING.LGPL +513 -0
  18. archive_r_python-0.1.36.dist-info/licenses/LICENSES/bzip2-LICENSE +42 -0
  19. archive_r_python-0.1.36.dist-info/licenses/LICENSES/libarchive-COPYING +65 -0
  20. archive_r_python-0.1.36.dist-info/licenses/LICENSES/libb2-COPYING +117 -0
  21. archive_r_python-0.1.36.dist-info/licenses/LICENSES/libomp-LICENSE.TXT +361 -0
  22. archive_r_python-0.1.36.dist-info/licenses/LICENSES/libxml2-Copyright +23 -0
  23. archive_r_python-0.1.36.dist-info/licenses/LICENSES/lz4-LICENSE +24 -0
  24. archive_r_python-0.1.36.dist-info/licenses/LICENSES/nettle-COPYING.LESSERv3 +165 -0
  25. archive_r_python-0.1.36.dist-info/licenses/LICENSES/nettle-COPYINGv2 +339 -0
  26. archive_r_python-0.1.36.dist-info/licenses/LICENSES/xz-COPYING +83 -0
  27. archive_r_python-0.1.36.dist-info/licenses/LICENSES/xz-COPYING.0BSD +11 -0
  28. archive_r_python-0.1.36.dist-info/licenses/LICENSES/zstd-LICENSE +30 -0
  29. archive_r_python-0.1.36.dist-info/licenses/NOTICE +116 -0
  30. archive_r_python-0.1.36.dist-info/top_level.txt +1 -0
Binary file
@@ -0,0 +1,633 @@
1
+ Metadata-Version: 2.4
2
+ Name: archive_r_python
3
+ Version: 0.1.36
4
+ Summary: Python bindings for archive_r: libarchive-based streaming traversal for recursive nested archives (no temp files, no large in-memory buffers)
5
+ Home-page: https://github.com/Raizo-TCS/archive_r
6
+ Author: archive_r Team
7
+ Author-email: raizo.tcs@users.noreply.github.com
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/Raizo-TCS/archive_r
10
+ Project-URL: Bug Tracker, https://github.com/Raizo-TCS/archive_r/issues
11
+ Project-URL: Documentation, https://github.com/Raizo-TCS/archive_r#readme
12
+ Project-URL: Releases, https://github.com/Raizo-TCS/archive_r/releases
13
+ Project-URL: Changelog, https://github.com/Raizo-TCS/archive_r/releases
14
+ Keywords: archive libarchive traversal nested multi-volume
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Operating System :: POSIX :: Linux
22
+ Classifier: Operating System :: MacOS
23
+ Classifier: Operating System :: Microsoft :: Windows
24
+ Classifier: Development Status :: 4 - Beta
25
+ Classifier: Topic :: System :: Archiving
26
+ Classifier: Topic :: Software Development :: Libraries
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ License-File: NOTICE
31
+ License-File: LICENSES/libarchive-COPYING
32
+ License-File: LICENSES/xz-COPYING
33
+ License-File: LICENSES/xz-COPYING.0BSD
34
+ License-File: LICENSES/nettle-COPYING.LESSERv3
35
+ License-File: LICENSES/nettle-COPYINGv2
36
+ License-File: LICENSES/attr-COPYING.LGPL
37
+ License-File: LICENSES/acl-COPYING.LGPL
38
+ License-File: LICENSES/libxml2-Copyright
39
+ License-File: LICENSES/lz4-LICENSE
40
+ License-File: LICENSES/zstd-LICENSE
41
+ License-File: LICENSES/libb2-COPYING
42
+ License-File: LICENSES/bzip2-LICENSE
43
+ License-File: LICENSES/libomp-LICENSE.TXT
44
+ Dynamic: description
45
+ Dynamic: description-content-type
46
+ Dynamic: license-file
47
+
48
+ # archive_r Python Bindings
49
+
50
+ > ⚠️ **Development Status**: This library is currently under development. The API may change without notice.
51
+
52
+ ## Overview
53
+
54
+ Python bindings for archive_r, a libarchive-based library for processing many archive formats.
55
+ It streams entry data directly from the source to recursively read nested archives without extracting to temporary files or loading large in-memory buffers.
56
+ The bindings expose a Pythonic iterator API with context manager support.
57
+
58
+ ---
59
+
60
+ ## Installation
61
+
62
+ ### From PyPI
63
+
64
+ ```bash
65
+ pip install archive_r_python
66
+ ```
67
+
68
+ ### From Source
69
+
70
+ ```bash
71
+ cd archive_r/bindings/python
72
+ pip install .
73
+ ```
74
+
75
+ ### Development Installation (Editable Mode)
76
+
77
+ ```bash
78
+ cd archive_r/bindings/python
79
+ pip install -e .
80
+ ```
81
+
82
+ ### Building with Parent Build Script
83
+
84
+ ```bash
85
+ cd archive_r
86
+ ./build.sh --with-python
87
+ ```
88
+
89
+ This builds the core library and Python bindings, placing artifacts in `build/bindings/python/`.
90
+
91
+ ---
92
+
93
+ ## Basic Usage
94
+
95
+ ### Simple Traversal
96
+
97
+ ```python
98
+ import archive_r
99
+
100
+ # Context manager ensures proper resource cleanup
101
+ with archive_r.Traverser("test.zip") as traverser:
102
+ for entry in traverser:
103
+ print(f"Path: {entry.path} (depth={entry.depth})")
104
+ if entry.is_file:
105
+ print(f" Size: {entry.size} bytes")
106
+ ```
107
+
108
+ ### Reading Entry Content
109
+
110
+ ```python
111
+ import archive_r
112
+
113
+ with archive_r.Traverser("archive.tar.gz") as traverser:
114
+ for entry in traverser:
115
+ if entry.is_file and entry.path.endswith('.txt'):
116
+ # Read full content
117
+ content = entry.read()
118
+ print(f"Content of {entry.path}:")
119
+ print(content.decode('utf-8', errors='replace'))
120
+ ```
121
+
122
+ ### Chunked Reading (Large Files)
123
+
124
+ ```python
125
+ import archive_r
126
+
127
+ with archive_r.Traverser("large_archive.zip") as traverser:
128
+ for entry in traverser:
129
+ if entry.is_file:
130
+ # Read in 8KB chunks
131
+ chunk_size = 8192
132
+ total_bytes = 0
133
+ while True:
134
+ chunk = entry.read(chunk_size)
135
+ if not chunk:
136
+ break
137
+ total_bytes += len(chunk)
138
+ # Process chunk...
139
+
140
+ print(f"{entry.path}: {total_bytes} bytes read")
141
+ ```
142
+
143
+ ### Searching in Entry Content
144
+
145
+ ```python
146
+ import archive_r
147
+
148
+ def search_in_entry(entry, keyword):
149
+ """Stream search within entry content (buffer boundary aware)"""
150
+ overlap = b''
151
+ buffer_size = 8192
152
+ keyword_bytes = keyword.encode('utf-8')
153
+
154
+ while True:
155
+ chunk = entry.read(buffer_size)
156
+ if not chunk:
157
+ break
158
+
159
+ search_text = overlap + chunk
160
+ if keyword_bytes in search_text:
161
+ return True
162
+
163
+ # Preserve tail for next iteration
164
+ if len(chunk) >= len(keyword_bytes) - 1:
165
+ overlap = chunk[-(len(keyword_bytes) - 1):]
166
+ else:
167
+ overlap = chunk
168
+
169
+ return False
170
+
171
+ with archive_r.Traverser("documents.zip") as traverser:
172
+ for entry in traverser:
173
+ if entry.is_file and entry.path.endswith('.txt'):
174
+ if search_in_entry(entry, "important"):
175
+ print(f"Found keyword in: {entry.path}")
176
+ ```
177
+
178
+ ### Controlling Archive Descent
179
+
180
+ ```python
181
+ import archive_r
182
+
183
+ with archive_r.Traverser("test.zip") as traverser:
184
+ for entry in traverser:
185
+ # Don't expand Office files (they are ZIP internally)
186
+ if entry.path.endswith(('.docx', '.xlsx', '.pptx')):
187
+ entry.set_descent(False)
188
+
189
+ print(f"Path: {entry.path}, Will descend: {entry.descent_enabled}")
190
+ ```
191
+
192
+ You can also disable automatic descent globally:
193
+
194
+ ```python
195
+ # Disable automatic descent for all entries
196
+ with archive_r.Traverser("test.zip", descend_archives=False) as traverser:
197
+ for entry in traverser:
198
+ # Manually enable descent for specific entries
199
+ if entry.path.endswith('.tar.gz'):
200
+ entry.set_descent(True)
201
+ ```
202
+
203
+ > ⚠️ **Note**: Reading entry content automatically disables descent. Call `entry.set_descent(True)` if you need to descend after reading.
204
+
205
+ ---
206
+
207
+ ## Path Representation
208
+
209
+ The Python bindings provide three ways to access entry paths:
210
+
211
+ ```python
212
+ with archive_r.Traverser("outer.zip") as traverser:
213
+ for entry in traverser:
214
+ # Full path including top-level archive
215
+ # Example: "outer.zip/inner.tar/file.txt"
216
+ print(f"path: {entry.path}")
217
+
218
+ # Last element of path_hierarchy
219
+ # Example: "inner.tar/file.txt"
220
+ print(f"name: {entry.name}")
221
+
222
+ # Path hierarchy as list
223
+ # Example: ["outer.zip", "inner.tar/file.txt"]
224
+ print(f"path_hierarchy: {entry.path_hierarchy}")
225
+ ```
226
+
227
+ `path_hierarchy` is particularly useful when you need custom path separators or want to represent the nesting structure explicitly.
228
+
229
+ ---
230
+
231
+ ## Metadata Access
232
+
233
+ ### Basic Metadata
234
+
235
+ Entry objects provide common metadata through properties:
236
+
237
+ ```python
238
+ with archive_r.Traverser("archive.tar") as traverser:
239
+ for entry in traverser:
240
+ print(f"Path: {entry.path}")
241
+ print(f" Type: {'file' if entry.is_file else 'directory'}")
242
+ print(f" Size: {entry.size} bytes")
243
+ print(f" Depth: {entry.depth}")
244
+ ```
245
+
246
+ ### Extended Metadata
247
+
248
+ For additional metadata (permissions, ownership, timestamps), specify `metadata_keys`:
249
+
250
+ ```python
251
+ with archive_r.Traverser("archive.tar", metadata_keys=["uid", "gid", "mtime", "mode"]) as traverser:
252
+ for entry in traverser:
253
+ # Retrieve all specified metadata as dictionary
254
+ metadata = entry.metadata()
255
+ print(f"{entry.path}:")
256
+ print(f" UID: {metadata.get('uid')}")
257
+ print(f" GID: {metadata.get('gid')}")
258
+ print(f" Mode: {oct(metadata.get('mode', 0))}")
259
+
260
+ # Or retrieve specific metadata
261
+ mtime = entry.find_metadata("mtime")
262
+ if mtime is not None:
263
+ print(f" Modified: {mtime}")
264
+ ```
265
+
266
+ Available metadata keys depend on the archive format. Common keys include:
267
+ - `uid`, `gid`: User/group ID
268
+ - `mtime`, `atime`, `ctime`: Timestamps (Unix time)
269
+ - `mode`: File permissions
270
+ - `uname`, `gname`: User/group names
271
+ - `hardlink`, `symlink`: Link targets
272
+
273
+ ---
274
+
275
+ ## Processing Split Archives
276
+
277
+ For split archive files (e.g., `.zip.001`, `.zip.002`), use `set_multi_volume_group()`:
278
+
279
+ ```python
280
+ import archive_r
281
+
282
+ with archive_r.Traverser("container.tar") as traverser:
283
+ for entry in traverser:
284
+ # Detect split archive parts
285
+ if '.part' in entry.path:
286
+ # Extract base name (e.g., "archive.zip.part001" → "archive.zip")
287
+ pos = entry.path.rfind('.part')
288
+ base_name = entry.path[:pos]
289
+ entry.set_multi_volume_group(base_name)
290
+
291
+ # After parent traversal, grouped parts are merged and expanded
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Format Specification
297
+
298
+ By default, all formats supported by libarchive are enabled. To restrict to specific formats:
299
+
300
+ ```python
301
+ # Enable only ZIP and TAR
302
+ with archive_r.Traverser("test.zip", formats=["zip", "tar"]) as traverser:
303
+ for entry in traverser:
304
+ print(entry.path)
305
+ ```
306
+
307
+ Common format names: `"7zip"`, `"ar"`, `"cab"`, `"cpio"`, `"iso9660"`, `"lha"`, `"rar"`, `"tar"`, `"warc"`, `"xar"`, `"zip"`
308
+
309
+ > 💡 **Tip**: Exclude pseudo-formats like `"mtree"` and `"raw"` if you encounter false positives on non-archive files.
310
+
311
+ ---
312
+
313
+ ## Custom Stream Factories
314
+
315
+ You can provide custom stream objects (file-like objects with `read()` method) to override the default file opening behavior:
316
+
317
+ ```python
318
+ import archive_r
319
+ import io
320
+
321
+ # Register a custom stream factory
322
+ def custom_stream_factory(path):
323
+ """Return a file-like object for the given path"""
324
+ if path == "special_file.bin":
325
+ # Return custom data source
326
+ return io.BytesIO(b"custom content")
327
+ # Return None to use default file opening
328
+ return None
329
+
330
+ archive_r.register_stream_factory(custom_stream_factory)
331
+
332
+ with archive_r.Traverser("test.zip") as traverser:
333
+ for entry in traverser:
334
+ # When traverser needs to open "special_file.bin",
335
+ # your factory will provide the BytesIO stream
336
+ pass
337
+ ```
338
+
339
+ Stream objects must provide:
340
+ - `read(size)`: Read up to `size` bytes
341
+ - Optional: `seek(offset, whence)`, `tell()` for seekable streams
342
+ - Optional: `rewind()` (defaults to `seek(0, 0)` if not provided)
343
+
344
+ ---
345
+
346
+ ## Error Handling
347
+
348
+ ### Fault Callbacks
349
+
350
+ Data errors (corrupted archives, I/O failures) are reported via callbacks without stopping traversal:
351
+
352
+ ```python
353
+ import archive_r
354
+
355
+ def fault_handler(fault_info):
356
+ """Called when data errors occur during traversal"""
357
+ print(f"Warning at {fault_info['hierarchy']}: {fault_info['message']}")
358
+ if fault_info.get('errno'):
359
+ print(f" Error code: {fault_info['errno']}")
360
+
361
+ archive_r.on_fault(fault_handler)
362
+
363
+ with archive_r.Traverser("potentially_corrupted.zip") as traverser:
364
+ for entry in traverser:
365
+ # Valid entries are processed normally
366
+ # Corrupted entries trigger fault_handler
367
+ print(entry.path)
368
+ ```
369
+
370
+ ### Read Errors
371
+
372
+ Errors during `read()` raise exceptions:
373
+
374
+ ```python
375
+ try:
376
+ with archive_r.Traverser("test.zip") as traverser:
377
+ for entry in traverser:
378
+ if entry.is_file:
379
+ content = entry.read()
380
+ except RuntimeError as e:
381
+ print(f"Read error: {e}")
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Thread Safety
387
+
388
+ The Python bindings follow the same thread safety constraints as the C++ core:
389
+
390
+ - ✓ **Thread-safe**: Each thread can create and use its own `Traverser` instance independently
391
+ - ✗ **Not thread-safe**: A single `Traverser` or `Entry` instance must not be shared across threads
392
+
393
+ ### Example
394
+
395
+ ```python
396
+ import threading
397
+ import archive_r
398
+
399
+ # ✓ SAFE: Each thread has its own Traverser
400
+ def worker():
401
+ with archive_r.Traverser("archive.tar.gz") as traverser:
402
+ for entry in traverser:
403
+ # Process entry...
404
+ pass
405
+
406
+ t1 = threading.Thread(target=worker)
407
+ t2 = threading.Thread(target=worker)
408
+ t1.start()
409
+ t2.start()
410
+ t1.join()
411
+ t2.join()
412
+
413
+ # ✗ UNSAFE: Sharing a single Traverser instance across threads
414
+ shared_traverser = archive_r.Traverser("archive.tar.gz")
415
+ def unsafe_worker():
416
+ for entry in shared_traverser: # Race condition!
417
+ pass
418
+
419
+ # Don't do this!
420
+ # t1 = threading.Thread(target=unsafe_worker)
421
+ # t2 = threading.Thread(target=unsafe_worker)
422
+ ```
423
+
424
+ Additionally:
425
+ - **Global registration functions** (`register_stream_factory`, `on_fault`) should be called during single-threaded initialization
426
+ - **Entry objects** should not be shared between threads (they are tied to the Traverser's internal state)
427
+
428
+ ---
429
+
430
+ ## Advanced Examples
431
+
432
+ ### Full Example: Recursive Archive Analyzer
433
+
434
+ ```python
435
+ import archive_r
436
+ import sys
437
+ from collections import defaultdict
438
+
439
+ def analyze_archive(archive_path):
440
+ """Analyze archive contents and print statistics"""
441
+ stats = defaultdict(int)
442
+ file_types = defaultdict(int)
443
+
444
+ with archive_r.Traverser(archive_path, metadata_keys=["mtime"]) as traverser:
445
+ for entry in traverser:
446
+ stats['total_entries'] += 1
447
+
448
+ if entry.is_file:
449
+ stats['files'] += 1
450
+ stats['total_size'] += entry.size
451
+
452
+ # Count by extension
453
+ if '.' in entry.name:
454
+ ext = entry.name.rsplit('.', 1)[1]
455
+ file_types[ext] += 1
456
+
457
+ # Find largest file
458
+ if entry.size > stats.get('max_file_size', 0):
459
+ stats['max_file_size'] = entry.size
460
+ stats['max_file_path'] = entry.path
461
+ else:
462
+ stats['directories'] += 1
463
+
464
+ # Track maximum depth
465
+ if entry.depth > stats.get('max_depth', 0):
466
+ stats['max_depth'] = entry.depth
467
+
468
+ # Print results
469
+ print(f"\nArchive Analysis: {archive_path}")
470
+ print(f" Total entries: {stats['total_entries']}")
471
+ print(f" Files: {stats['files']}")
472
+ print(f" Directories: {stats['directories']}")
473
+ print(f" Total size: {stats['total_size']:,} bytes")
474
+ print(f" Maximum depth: {stats['max_depth']}")
475
+
476
+ if 'max_file_path' in stats:
477
+ print(f" Largest file: {stats['max_file_path']} ({stats['max_file_size']:,} bytes)")
478
+
479
+ if file_types:
480
+ print("\n File types:")
481
+ for ext, count in sorted(file_types.items(), key=lambda x: x[1], reverse=True)[:10]:
482
+ print(f" .{ext}: {count}")
483
+
484
+ if __name__ == '__main__':
485
+ if len(sys.argv) < 2:
486
+ print("Usage: python analyze.py <archive_path>")
487
+ sys.exit(1)
488
+
489
+ analyze_archive(sys.argv[1])
490
+ ```
491
+
492
+ ---
493
+
494
+ ## Testing
495
+
496
+ Run the Python binding tests:
497
+
498
+ ```bash
499
+ cd archive_r/bindings/python
500
+ python -m unittest discover test
501
+ ```
502
+
503
+ Or use the project-wide test runner:
504
+
505
+ ```bash
506
+ cd archive_r
507
+ ./bindings/python/run_binding_tests.sh
508
+ ```
509
+
510
+ ---
511
+
512
+ ## API Reference
513
+
514
+ ### Module: `archive_r`
515
+
516
+ #### Class: `Traverser`
517
+
518
+ Constructor:
519
+ ```python
520
+ Traverser(
521
+ roots, # str or list of str/list (path hierarchy)
522
+ formats=None, # list of format names (default: all)
523
+ descend_archives=True, # automatically expand archives
524
+ metadata_keys=None, # list of metadata keys to capture
525
+ passphrases=None # list of passphrases for encrypted archives
526
+ )
527
+ ```
528
+
529
+ Methods:
530
+ - `__iter__()`: Returns self (iterator protocol)
531
+ - `__next__()`: Returns next `Entry` or raises `StopIteration`
532
+ - `__enter__()`: Context manager entry (returns self)
533
+ - `__exit__(exc_type, exc_val, exc_tb)`: Context manager exit
534
+
535
+ #### Class: `Entry`
536
+
537
+ Properties:
538
+ - `path`: Full path string (read-only)
539
+ - `name`: Last element of path hierarchy (read-only)
540
+ - `path_hierarchy`: List representation of path (read-only)
541
+ - `depth`: Nesting depth (read-only)
542
+ - `is_file`: True if entry is a file (read-only)
543
+ - `size`: File size in bytes, 0 for directories (read-only)
544
+ - `descent_enabled`: Whether this entry will be expanded as an archive (read-only)
545
+
546
+ Methods:
547
+ - `read(size=None)`: Read entry content (bytes). If `size` is omitted, reads all remaining data
548
+ - `set_descent(enabled)`: Enable/disable archive expansion for this entry
549
+ - `set_multi_volume_group(group_name)`: Register this entry as part of a split archive group
550
+ - `metadata()`: Return dictionary of all captured metadata
551
+ - `find_metadata(key)`: Return value for specific metadata key, or None if not found
552
+
553
+ #### Function: `register_stream_factory`
554
+
555
+ ```python
556
+ archive_r.register_stream_factory(factory_func)
557
+ ```
558
+
559
+ Register a callback to provide custom stream objects for file access.
560
+
561
+ **Parameters**:
562
+ - `factory_func`: Callable that takes a file path (str) and returns a file-like object or None
563
+
564
+ **Stream object requirements**:
565
+ - Must provide `read(size)` method
566
+ - Optional: `seek(offset, whence)`, `tell()`, `rewind()`
567
+
568
+ #### Function: `on_fault`
569
+
570
+ ```python
571
+ archive_r.on_fault(callback)
572
+ ```
573
+
574
+ Register a callback to receive fault notifications during traversal.
575
+
576
+ **Parameters**:
577
+ - `callback`: Callable that takes a dict with keys:
578
+ - `hierarchy`: List of path components where fault occurred
579
+ - `message`: Human-readable error description
580
+ - `errno`: Optional error number from system calls
581
+
582
+ ---
583
+
584
+ ## Packaging
585
+
586
+ ### Building Wheels
587
+
588
+ ```bash
589
+ cd archive_r
590
+ ./build.sh --package-python
591
+ ```
592
+
593
+ This creates wheel (`.whl`) and source distribution (`.tar.gz`) in `build/bindings/python/dist/`.
594
+
595
+ ### Manual Packaging
596
+
597
+ ```bash
598
+ cd bindings/python
599
+ python setup.py sdist bdist_wheel
600
+ ```
601
+
602
+ ---
603
+
604
+ ## Requirements
605
+
606
+ - Python 3.8 or later
607
+ - libarchive 3.x (runtime dependency)
608
+ - setuptools, wheel (build dependencies)
609
+ - pybind11 >= 2.6.0 (build dependency, automatically vendored during packaging)
610
+
611
+ ---
612
+
613
+ ## License
614
+
615
+ The Python bindings are distributed under the MIT License, consistent with the archive_r core library.
616
+
617
+ ### Third-Party Licenses
618
+
619
+ - **pybind11**: BSD-style License (used for C++/Python interfacing)
620
+ - **libarchive**: New BSD License (runtime dependency)
621
+
622
+ ---
623
+
624
+ ## See Also
625
+
626
+ - [archive_r Core Documentation](../../README.md)
627
+ - [Ruby Bindings](../ruby/README.md)
628
+ - [Example Scripts](examples/)
629
+
630
+ ---
631
+
632
+
633
+
@@ -0,0 +1,30 @@
1
+ archive_r.cp315-win_amd64.pyd,sha256=1YdUboKAy1M9o6Jdw5VuEFGI1fWNWthQX2kmOsRMR5Y,297472
2
+ archive_r_python-0.1.36.data/data/.libs/archive.dll,sha256=S9uqdvVc4J6H60K8jxlb3PXBuE_0S5N6NBIs5EkYuBc,790016
3
+ archive_r_python-0.1.36.data/data/.libs/archive_r_core.dll,sha256=g_PJSvVasAvanY9yiEWz-CgI3QpSHtgthS1RfweJPLs,1292288
4
+ archive_r_python-0.1.36.data/data/.libs/bz2.dll,sha256=x2ddr7WXE9lA458t8peygzqQZLBJhXvqyUhkld27eu0,76800
5
+ archive_r_python-0.1.36.data/data/.libs/legacy.dll,sha256=z9w5-Di0tOYn-4VQiWd_Fa8vMI2RUvO8UvTIAld0X88,108032
6
+ archive_r_python-0.1.36.data/data/.libs/libcrypto-3-x64.dll,sha256=IQTEuySlH1yqI3r6oDh8Vb8OGs4FPwaDIBybyQ6FnZY,5226496
7
+ archive_r_python-0.1.36.data/data/.libs/liblzma.dll,sha256=IiFg_HR_DdgaPogKAnzaNfLWUnq9tFZH0kgxO--6QzY,189440
8
+ archive_r_python-0.1.36.data/data/.libs/libssl-3-x64.dll,sha256=h9eFKXeAeOTAB_3drk1UQ1zzoAPpRiyXimzoF9lh30s,859136
9
+ archive_r_python-0.1.36.data/data/.libs/lz4.dll,sha256=qGhShQYOPBet5F_Z7Ud15OdIvN_UBXTG3L1Ijrudzoc,126464
10
+ archive_r_python-0.1.36.data/data/.libs/z.dll,sha256=4CyL1qFJsznNe6tsFBEO4qDQGZ4TCiThJfQwa-K0ZwM,91136
11
+ archive_r_python-0.1.36.data/data/.libs/zstd.dll,sha256=_H26-_IDLGODoNWZd1drz2TKpoMsHQGyTxDzoViLSCs,657920
12
+ archive_r_python-0.1.36.dist-info/licenses/LICENSE,sha256=4QR2Yx_UgB9v1Cm3TzmDCpuVsPacvDYk0wQcwVflCTk,1092
13
+ archive_r_python-0.1.36.dist-info/licenses/NOTICE,sha256=M_SNLflDZkKz-K5lZ3-vfyy2m5WqRedgbRt8NA_Ivpc,5386
14
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/acl-COPYING.LGPL,sha256=POS7vgFVNjte3DRGzmL2l8rbIcadvEhPhBByR9NvZWk,27275
15
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/attr-COPYING.LGPL,sha256=REMu1WEnYzRpBDrA8FHIrUR-ItIHF-TBLmfWkGZKvXA,27276
16
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/bzip2-LICENSE,sha256=kQK3AJHoQWjEaNV9zKZOwfYMds77yDtm4XVHJbndkM8,1938
17
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/libarchive-COPYING,sha256=Y_wFJ_D0FMrAeNqroZn4cnYDct0_yx-QLxL7ytpR-Ys,3151
18
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/libb2-COPYING,sha256=qVUOiTB5H7Sc4AO1CQIR-icrxvBipxY53p-nZrsGEDE,6673
19
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/libomp-LICENSE.TXT,sha256=39dNXx_UuID6HfDgcFb0bPbA5H-TqhWWy8s7mrI3WMg,20102
20
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/libxml2-Copyright,sha256=kmZzIxwAHuqPSFOD4suAr-e-sMAc_EXVzxZT52-0Ois,1296
21
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/lz4-LICENSE,sha256=hiyILuWOr_2--054mq7Cpe0DEDFb6YnEQEx3gn6n_pY,1335
22
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/nettle-COPYING.LESSERv3,sha256=MZkX9czQmHjbb2fJp33uhGBVZEyknrU1YoueAgqHJh4,7804
23
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/nettle-COPYINGv2,sha256=GJsa-V1mEVHgVM6hDJGz11Tk3k0_7PsHTB-ylHb3Fns,18431
24
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/xz-COPYING,sha256=N9GwkERgRdPodBhjGmwKwIpeSchKZm2L4In1UrDEjkk,3968
25
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/xz-COPYING.0BSD,sha256=VKQj97LIkKHk1lQfiPTlcCwEFJiLo_Ykf0V407i8pbc,618
26
+ archive_r_python-0.1.36.dist-info/licenses/LICENSES/zstd-LICENSE,sha256=07qtwuHden4LSDYz5rOXQ8FqFvKNaWuv9Qf81FHz1K0,1579
27
+ archive_r_python-0.1.36.dist-info/METADATA,sha256=FTU2QdiYnK0MZUr_aJS9kWHt1i2C6ONXEDygiGyRIzI,18644
28
+ archive_r_python-0.1.36.dist-info/WHEEL,sha256=geuJoH1aW6kS7q0cKuoOBUdE-tpBsTvRf9l5rOGbxQs,101
29
+ archive_r_python-0.1.36.dist-info/top_level.txt,sha256=75YnLKM4lTfPE6yWlOtt9wzizXtUFJf9OG8ImtwVmUg,10
30
+ archive_r_python-0.1.36.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp315-cp315-win_amd64
5
+