multiarchive 0.1.2__tar.gz → 0.1.4__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.
@@ -1,269 +1,284 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: multiarchive
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A high level archive handler for Python.
5
5
  Author: NSPC911
6
6
  Author-email: NSPC911 <87571998+NSPC911@users.noreply.github.com>
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
 
10
- # multiarchive
11
-
12
- A high level abstraction of multiple archive formats
13
-
14
- ## Installation
15
-
16
- ```bash
17
- uv add multiarchive
18
- ```
19
-
20
- Optional dependency for RAR support:
21
-
22
- ```bash
23
- uv add rarfile
24
- ```
25
-
26
- ZStandard support requires the `backports-zstd` package on Python <3.14:
27
-
28
- ```bash
29
- uv add "backports-zstd; python_version < '3.14'"
30
- ```
31
-
32
- ## Quick Start
33
-
34
- ```python
35
- from multiarchive import Archive
36
-
37
- # Open and inspect
38
- with Archive("archive.zip") as arc:
39
- print(arc.namelist()) # list of member names
40
- print(len(arc)) # number of members
41
- print(arc.size) # total uncompressed size in bytes
42
-
43
- # Extract a single file
44
- with Archive("archive.tar.gz") as arc:
45
- arc.extract("docs/readme.md", path="./output")
46
-
47
- # Read file contents without extracting
48
- with Archive("archive.zip") as arc:
49
- f = arc.open("config.json")
50
- content = f.read()
51
- f.close()
52
-
53
- # Iterate over members
54
- with Archive("archive.zip") as arc:
55
- for name in arc:
56
- if arc.is_file(name):
57
- print(f" {name}")
58
- ```
59
-
60
- ## Supported Formats
61
-
62
- | Format | Read | Write | Extensions |
63
- | ------- | ---- | ----- | --------------------------- |
64
- | ZIP | Yes | Yes | `.zip` |
65
- | TAR | Yes | Yes | `.tar` |
66
- | TAR.GZ | Yes | Yes | `.tgz`, `.tar.gz` |
67
- | TAR.BZ2 | Yes | Yes | `.tbz`, `.tbz2`, `.tar.bz2` |
68
- | TAR.XZ | Yes | Yes | `.tar.xz`, `.tar.lzma` |
69
- | TAR.ZST | Yes | Yes | `.tzst`, `.tar.zst` |
70
- | RAR | Yes | No | `.rar` |
71
-
72
- RAR support requires the `rarfile` package and an external `unrar` tool.
73
-
74
- ## Reading Archives
75
-
76
- ### Opening
77
-
78
- Use the context manager (recommended):
79
-
80
- ```python
81
- with Archive("file.zip") as arc:
82
- ...
83
- ```
84
-
85
- Or the classmethod:
86
-
87
- ```python
88
- arc = Archive.open_archive("file.zip")
89
- try:
90
- arc.namelist()
91
- finally:
92
- arc.close()
93
- ```
94
-
95
- Reading is supported on all formats, but password-protected archives are not supported and will raise a `ValueError`.
96
- If `algo` isnt explicitly provided, it will attempt to open it as ZIP first, then TAR, then RAR (if `rarfile` is installed). If all fails, it raises a ValueError.
97
-
98
- ### Listing Members
99
-
100
- ```python
101
- with Archive("file.zip") as arc:
102
- names = arc.namelist() # Just names
103
- names = arc.members # alias to namelist
104
-
105
- # extra info (ArchiveMemberInfo objects)
106
- for info in arc.infolist():
107
- print(f"{info.name}: {info.uncompressed_size} bytes, mtime={info.mtime}")
108
- ```
109
-
110
- ### Checking Membership
111
-
112
- ```python
113
- with Archive("file.zip") as arc:
114
- if "config.json" in arc:
115
- print("Found!")
116
-
117
- for name in arc:
118
- if arc.is_file(name):
119
- ...
120
- if arc.is_dir(name):
121
- ...
122
- ```
123
-
124
- ### Extracting
125
-
126
- ```python
127
- with Archive("file.tar.gz") as arc:
128
- # Single file
129
- arc.extract("src/main.py", path="./extracted")
130
- ```
131
-
132
- ### Reading Member Contents
133
-
134
- ```python
135
- with Archive("file.zip") as arc:
136
- f = arc.open("data.json")
137
- if f is not None:
138
- content = f.read()
139
- f.close()
140
- ```
141
-
142
- ## Writing Archives
143
-
144
- ### Creating a New Archive
145
-
146
- ```python
147
- from multiarchive import Archive
148
-
149
- # ZIP
150
- with Archive("output.zip", mode="w") as arc:
151
- ...
152
-
153
- # TAR.GZ with compression level
154
- with Archive("output.tar.gz", mode="w", compression_level=9) as arc:
155
- ...
156
- ```
157
-
158
- The format is determined by file extension. Use the `algo` parameter to override:
159
-
160
- ```python
161
- with Archive("my_archive", mode="w", algo="tar.xz") as arc:
162
- ...
163
- ```
164
-
165
- ### Compression Levels
166
-
167
- | Format | Range | Default |
168
- | ------- | ----- | ------- |
169
- | ZIP | 0–9 | 6 |
170
- | TAR.GZ | 0–9 | 6 |
171
- | TAR.BZ2 | 1–9 | 9 |
172
- | TAR.XZ | 0–9 | 6 |
173
- | TAR.ZST | 1–22 | 3 |
174
-
175
- ## File Information Object
176
-
177
- The `infolist()` method returns `ArchiveMemberInfo` objects with a unified interface across all formats:
178
-
179
- | Field | Type | Description |
180
- | ------------------- | ------------------------------- | -------------------------------------- |
181
- | `name` | `str` | Member filename |
182
- | `uncompressed_size` | `int` | Original file size in bytes |
183
- | `compressed_size` | `int \| None` | Compressed size (`None` for TAR) |
184
- | `mtime` | `float` | Modification time as epoch seconds |
185
- | `mode` | `int \| None` | Unix permission bits |
186
- | `is_dir` | `bool` | Whether the member is a directory |
187
- | `raw` | `ZipInfo \| TarInfo \| RarInfo` | Original backend object (escape hatch) |
188
-
189
- Factory methods for direct conversion (you shouldn't need these in normal usage):
190
-
191
- ```python
192
- info = ArchiveMemberInfo.from_zipinfo(zip_info)
193
- info = ArchiveMemberInfo.from_tarinfo(tar_info)
194
- info = ArchiveMemberInfo.from_rarinfo(rar_info)
195
- ```
196
-
197
- ## Properties
198
-
199
- | Method/Property | Description |
200
- | ----------------------- | --------------------------------------------------------------- |
201
- | `members` | Alias for `namelist()` |
202
- | `size` | Total uncompressed size of all members |
203
- | `comment` / `comment=` | Get/set archive comment (ZIP only, raises ValueError otherwise) |
204
-
205
- ## Error Handling
206
-
207
- ```python
208
- from multiarchive import Archive, BadArchiveError
209
-
210
- try:
211
- with Archive("corrupted.zip") as arc:
212
- arc.namelist()
213
- except BadArchiveError as e:
214
- print(f"Archive error: {e}")
215
- except NotImplementedError as e:
216
- # password-protected (not supported)
217
- print(f"Not implemented: {e}")
218
- except ValueError as e:
219
- # Unknown format
220
- print(f"Value error: {e}")
221
- except FileNotFoundError:
222
- print("File not found")
223
- ```
224
-
225
- ## API Reference
226
-
227
- ### `Archive`
228
-
229
- ```python
230
- Archive(
231
- filename: str | Path,
232
- mode: str = "r",
233
- compression_level: int | None = None,
234
- )
235
- ```
236
-
237
- | Parameter | Description |
238
- | ------------------- | ------------------------------------------------- |
239
- | `filename` | Path to the archive file |
240
- | `mode` | `'r'` for read, `'w'` for write, `'a'` for append |
241
- | `compression_level` | Compression level (format-dependent) |
242
-
243
- #### Methods
244
-
245
- | Method | Description |
246
- | ------------------------------------------------------ | -------------------------------------- |
247
- | `open_archive(cls, filename, mode, compression_level)` | Factory method, returns opened archive |
248
- | `namelist()` | List of member names |
249
- | `infolist()` | List of `ArchiveMemberInfo` objects |
250
- | `extract(member, path)` | Extract single member |
251
- | `open(member, mode)` | Open member as file-like object |
252
- | `is_dir(member)` | Check if member is a directory |
253
- | `is_file(member)` | Check if member is a file |
254
- | `close()` | Explicitly close the archive |
255
-
256
- ### `BadArchiveError`
257
-
258
- Raised when an archive file is corrupt or in an unsupported format.
259
-
260
- ### `ArchiveExtensions`
261
-
262
- Frozen dataclass mapping format names to their common file extensions:
263
-
264
- ```python
265
- ArchiveExtensions.zip # (".zip",)
266
- ArchiveExtensions.gz # (".tgz", ".tar.gz")
267
- ArchiveExtensions.xz # (".tar.xz", ".tar.lzma")
268
- # ... etc
269
- ```
10
+ # multiarchive
11
+
12
+ A high level abstraction of multiple archive formats
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ uv add multiarchive
18
+ ```
19
+
20
+ Optional dependency for RAR support:
21
+
22
+ ```bash
23
+ uv add rarfile
24
+ ```
25
+
26
+ ZStandard support requires the `backports-zstd` package on Python <3.14:
27
+
28
+ ```bash
29
+ uv add "backports-zstd; python_version < '3.14'"
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```python
35
+ from multiarchive import Archive
36
+
37
+ # Open and inspect
38
+ with Archive("archive.zip") as arc:
39
+ print(arc.namelist()) # list of member names
40
+ print(len(arc)) # number of members
41
+ print(arc.size) # total uncompressed size in bytes
42
+
43
+ # Extract a single file
44
+ with Archive("archive.tar.gz") as arc:
45
+ arc.extract("docs/readme.md", path="./output")
46
+
47
+ # Read file contents without extracting
48
+ with Archive("archive.zip") as arc:
49
+ f = arc.open("config.json")
50
+ content = f.read()
51
+ f.close()
52
+
53
+ # Iterate over members
54
+ with Archive("archive.zip") as arc:
55
+ for name in arc:
56
+ if arc.is_file(name):
57
+ print(f" {name}")
58
+ ```
59
+
60
+ ## Supported Formats
61
+
62
+ | Format | Read | Write | Extensions |
63
+ | ------- | ---- | ----- | --------------------------- |
64
+ | ZIP | Yes | Yes | `.zip` |
65
+ | TAR | Yes | Yes | `.tar` |
66
+ | TAR.GZ | Yes | Yes | `.tgz`, `.tar.gz` |
67
+ | TAR.BZ2 | Yes | Yes | `.tbz`, `.tbz2`, `.tar.bz2` |
68
+ | TAR.XZ | Yes | Yes | `.tar.xz`, `.tar.lzma` |
69
+ | TAR.ZST | Yes | Yes | `.tzst`, `.tar.zst` |
70
+ | RAR | Yes | No | `.rar` |
71
+
72
+ RAR support requires the `rarfile` package and an external `unrar` tool.
73
+
74
+ ## Reading Archives
75
+
76
+ ### Opening
77
+
78
+ Use the context manager (recommended):
79
+
80
+ ```python
81
+ with Archive("file.zip") as arc:
82
+ ...
83
+ ```
84
+
85
+ Or the classmethod:
86
+
87
+ ```python
88
+ arc = Archive.open_archive("file.zip")
89
+ try:
90
+ arc.namelist()
91
+ finally:
92
+ arc.close()
93
+ ```
94
+
95
+ Reading is supported on all formats, but password-protected archives are not supported and will raise a `ValueError`.
96
+ If `algo` isnt explicitly provided, it will attempt to open it as ZIP first, then TAR, then RAR (if `rarfile` is installed). If all fails, it raises a ValueError.
97
+
98
+ ### Listing Members
99
+
100
+ ```python
101
+ with Archive("file.zip") as arc:
102
+ names = arc.namelist() # Just names
103
+ names = arc.members # alias to namelist
104
+
105
+ # extra info (ArchiveMemberInfo objects)
106
+ for info in arc.infolist():
107
+ print(f"{info.name}: {info.uncompressed_size} bytes, mtime={info.mtime}")
108
+ ```
109
+
110
+ ### Checking Membership
111
+
112
+ ```python
113
+ with Archive("file.zip") as arc:
114
+ if "config.json" in arc:
115
+ print("Found!")
116
+
117
+ for name in arc:
118
+ if arc.is_file(name):
119
+ ...
120
+ if arc.is_dir(name):
121
+ ...
122
+ ```
123
+
124
+ ### Extracting
125
+
126
+ ```python
127
+ with Archive("file.tar.gz") as arc:
128
+ # Single file
129
+ arc.extract("src/main.py", path="./extracted")
130
+ ```
131
+
132
+ ### Reading Member Contents
133
+
134
+ ```python
135
+ with Archive("file.zip") as arc:
136
+ f = arc.open("data.json")
137
+ if f is not None:
138
+ content = f.read()
139
+ f.close()
140
+ ```
141
+
142
+ ## Writing Archives
143
+
144
+ ### Creating a New Archive
145
+
146
+ ```python
147
+ from multiarchive import Archive
148
+
149
+ # ZIP
150
+ with Archive("output.zip", mode="w") as arc:
151
+ ...
152
+
153
+ # TAR.GZ with compression level
154
+ with Archive("output.tar.gz", mode="w", compression_level=9) as arc:
155
+ ...
156
+ ```
157
+
158
+ Add members with an optional name to use inside the archive:
159
+
160
+ ```python
161
+ with Archive("output.zip", mode="w") as arc:
162
+ arc.add("src/main.py", arcname="main.py")
163
+ ```
164
+
165
+ ZIP timestamps are limited to the years 1980 through 2107. To clamp timestamps
166
+ outside that range instead of raising an error:
167
+
168
+ ```python
169
+ with Archive("output.zip", mode="w", strict_timestamps=False) as arc:
170
+ arc.add("old-file.txt")
171
+ ```
172
+
173
+ The format is determined by file extension. Use the `algo` parameter to override:
174
+
175
+ ```python
176
+ with Archive("my_archive", mode="w", algo="tar.xz") as arc:
177
+ ...
178
+ ```
179
+
180
+ ### Compression Levels
181
+
182
+ | Format | Range | Default |
183
+ | ------- | ----- | ------- |
184
+ | ZIP | 0–9 | 6 |
185
+ | TAR.GZ | 0–9 | 6 |
186
+ | TAR.BZ2 | 1–9 | 9 |
187
+ | TAR.XZ | 0–9 | 6 |
188
+ | TAR.ZST | 1–22 | 3 |
189
+
190
+ ## File Information Object
191
+
192
+ The `infolist()` method returns `ArchiveMemberInfo` objects with a unified interface across all formats:
193
+
194
+ | Field | Type | Description |
195
+ | ------------------- | ------------------------------- | -------------------------------------- |
196
+ | `name` | `str` | Member filename |
197
+ | `uncompressed_size` | `int` | Original file size in bytes |
198
+ | `compressed_size` | `int \| None` | Compressed size (`None` for TAR) |
199
+ | `mtime` | `float` | Modification time as epoch seconds |
200
+ | `mode` | `int \| None` | Unix permission bits |
201
+ | `is_dir` | `bool` | Whether the member is a directory |
202
+ | `raw` | `ZipInfo \| TarInfo \| RarInfo` | Original backend object (escape hatch) |
203
+
204
+ Factory methods for direct conversion (you shouldn't need these in normal usage):
205
+
206
+ ```python
207
+ info = ArchiveMemberInfo.from_zipinfo(zip_info)
208
+ info = ArchiveMemberInfo.from_tarinfo(tar_info)
209
+ info = ArchiveMemberInfo.from_rarinfo(rar_info)
210
+ ```
211
+
212
+ ## Properties
213
+
214
+ | Method/Property | Description |
215
+ | ----------------------- | --------------------------------------------------------------- |
216
+ | `members` | Alias for `namelist()` |
217
+ | `size` | Total uncompressed size of all members |
218
+ | `comment` / `comment=` | Get/set archive comment (ZIP only, raises ValueError otherwise) |
219
+
220
+ ## Error Handling
221
+
222
+ ```python
223
+ from multiarchive import Archive, BadArchiveError
224
+
225
+ try:
226
+ with Archive("corrupted.zip") as arc:
227
+ arc.namelist()
228
+ except BadArchiveError as e:
229
+ print(f"Archive error: {e}")
230
+ except NotImplementedError as e:
231
+ # password-protected (not supported)
232
+ print(f"Not implemented: {e}")
233
+ except ValueError as e:
234
+ # Unknown format
235
+ print(f"Value error: {e}")
236
+ except FileNotFoundError:
237
+ print("File not found")
238
+ ```
239
+
240
+ ## API Reference
241
+
242
+ ### `Archive`
243
+
244
+ ```python
245
+ Archive(
246
+ filename: str | Path,
247
+ mode: str = "r",
248
+ compression_level: int | None = None,
249
+ )
250
+ ```
251
+
252
+ | Parameter | Description |
253
+ | ------------------- | ------------------------------------------------- |
254
+ | `filename` | Path to the archive file |
255
+ | `mode` | `'r'` for read, `'w'` for write, `'a'` for append |
256
+ | `compression_level` | Compression level (format-dependent) |
257
+
258
+ #### Methods
259
+
260
+ | Method | Description |
261
+ | ------------------------------------------------------ | -------------------------------------- |
262
+ | `open_archive(cls, filename, mode, compression_level)` | Factory method, returns opened archive |
263
+ | `namelist()` | List of member names |
264
+ | `infolist()` | List of `ArchiveMemberInfo` objects |
265
+ | `extract(member, path)` | Extract single member |
266
+ | `open(member, mode)` | Open member as file-like object |
267
+ | `is_dir(member)` | Check if member is a directory |
268
+ | `is_file(member)` | Check if member is a file |
269
+ | `close()` | Explicitly close the archive |
270
+
271
+ ### `BadArchiveError`
272
+
273
+ Raised when an archive file is corrupt or in an unsupported format.
274
+
275
+ ### `ArchiveExtensions`
276
+
277
+ Frozen dataclass mapping format names to their common file extensions:
278
+
279
+ ```python
280
+ ArchiveExtensions.zip # (".zip",)
281
+ ArchiveExtensions.gz # (".tgz", ".tar.gz")
282
+ ArchiveExtensions.xz # (".tar.xz", ".tar.lzma")
283
+ # ... etc
284
+ ```