py7zz 0.1.0.dev22__py3-none-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.
- py7zz/__init__.py +199 -0
- py7zz/async_ops.py +323 -0
- py7zz/binaries/windows/7zz.exe +0 -0
- py7zz/bundled_info.py +122 -0
- py7zz/cli.py +90 -0
- py7zz/compression.py +128 -0
- py7zz/config.py +260 -0
- py7zz/core.py +413 -0
- py7zz/exceptions.py +141 -0
- py7zz/simple.py +357 -0
- py7zz/updater.py +294 -0
- py7zz/version.py +265 -0
- py7zz-0.1.0.dev22.dist-info/METADATA +404 -0
- py7zz-0.1.0.dev22.dist-info/RECORD +17 -0
- py7zz-0.1.0.dev22.dist-info/WHEEL +4 -0
- py7zz-0.1.0.dev22.dist-info/entry_points.txt +2 -0
- py7zz-0.1.0.dev22.dist-info/licenses/LICENSE +52 -0
py7zz/cli.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command Line Interface Module
|
|
3
|
+
|
|
4
|
+
Directly passes through to the official 7zz binary, ensuring users get complete official 7-Zip functionality.
|
|
5
|
+
py7zz's value is in automatic binary management and providing Python API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from .bundled_info import get_version_info
|
|
14
|
+
from .core import find_7z_binary
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def print_version_info(format_type: str = "human") -> None:
|
|
18
|
+
"""Print version information in specified format."""
|
|
19
|
+
try:
|
|
20
|
+
info = get_version_info()
|
|
21
|
+
|
|
22
|
+
if format_type == "json":
|
|
23
|
+
print(json.dumps(info, indent=2))
|
|
24
|
+
else:
|
|
25
|
+
print(f"py7zz version: {info['py7zz_version']}")
|
|
26
|
+
print(f"Bundled 7zz version: {info['bundled_7zz_version']}")
|
|
27
|
+
print(f"Release type: {info['release_type']}")
|
|
28
|
+
print(f"Release date: {info['release_date']}")
|
|
29
|
+
print(f"GitHub tag: {info['github_tag']}")
|
|
30
|
+
print(f"Changelog: {info['changelog_url']}")
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f"py7zz error: {e}", file=sys.stderr)
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main() -> None:
|
|
37
|
+
"""
|
|
38
|
+
Main entry point: Handle py7zz-specific commands or pass through to official 7zz
|
|
39
|
+
|
|
40
|
+
This ensures:
|
|
41
|
+
1. Users get complete official 7zz functionality
|
|
42
|
+
2. py7zz-specific commands are handled properly
|
|
43
|
+
3. No need to maintain parameter mapping and feature synchronization
|
|
44
|
+
4. py7zz focuses on Python API and binary management
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
# Handle py7zz-specific commands
|
|
48
|
+
if len(sys.argv) > 1:
|
|
49
|
+
command = sys.argv[1]
|
|
50
|
+
|
|
51
|
+
if command == "version":
|
|
52
|
+
# Handle version command
|
|
53
|
+
format_type = "human"
|
|
54
|
+
if len(sys.argv) > 2 and sys.argv[2] == "--format":
|
|
55
|
+
if len(sys.argv) > 3:
|
|
56
|
+
format_type = sys.argv[3]
|
|
57
|
+
else:
|
|
58
|
+
print("Error: --format requires a value (human or json)", file=sys.stderr)
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
|
|
61
|
+
print_version_info(format_type)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
elif command in ["--py7zz-version", "-V"]:
|
|
65
|
+
# Handle quick version command
|
|
66
|
+
print_version_info("human")
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Get py7zz-managed 7zz binary
|
|
70
|
+
binary_path = find_7z_binary()
|
|
71
|
+
|
|
72
|
+
# Direct pass-through of all command line arguments
|
|
73
|
+
cmd = [binary_path] + sys.argv[1:]
|
|
74
|
+
|
|
75
|
+
# Use exec to replace current process, ensuring signal handling behavior is consistent with native 7zz
|
|
76
|
+
if os.name == "nt": # Windows
|
|
77
|
+
# Use subprocess on Windows and wait for result
|
|
78
|
+
result = subprocess.run(cmd)
|
|
79
|
+
sys.exit(result.returncode)
|
|
80
|
+
else: # Unix-like systems
|
|
81
|
+
# Use execv to replace process on Unix
|
|
82
|
+
os.execv(binary_path, cmd)
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"py7zz error: {e}", file=sys.stderr)
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
main()
|
py7zz/compression.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compression Algorithm Interface Module
|
|
3
|
+
|
|
4
|
+
Provides a simple interface similar to modern compression libraries for single-stream compression.
|
|
5
|
+
This is a complement to SevenZipFile archive functionality, not a replacement.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Union
|
|
11
|
+
|
|
12
|
+
from .core import run_7z
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def compress(data: Union[str, bytes], algorithm: str = "lzma2", level: int = 5) -> bytes:
|
|
16
|
+
"""
|
|
17
|
+
Compress a single data block, similar to zstd.compress()
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
data: Data to compress
|
|
21
|
+
algorithm: Compression algorithm (lzma2, lzma, ppmd, bzip2, deflate)
|
|
22
|
+
level: Compression level (0-9)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Compressed byte data
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(data, str):
|
|
28
|
+
data = data.encode("utf-8")
|
|
29
|
+
|
|
30
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
31
|
+
tmpdir_path = Path(tmpdir)
|
|
32
|
+
|
|
33
|
+
# Write to temporary file
|
|
34
|
+
input_file = tmpdir_path / "input.dat"
|
|
35
|
+
input_file.write_bytes(data)
|
|
36
|
+
|
|
37
|
+
# Compress as 7z single-file archive
|
|
38
|
+
output_file = tmpdir_path / "output.7z"
|
|
39
|
+
args = [
|
|
40
|
+
"a",
|
|
41
|
+
str(output_file),
|
|
42
|
+
str(input_file),
|
|
43
|
+
f"-mx{level}",
|
|
44
|
+
f"-m0={algorithm}",
|
|
45
|
+
"-ms=off", # Disable solid mode
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
run_7z(args)
|
|
49
|
+
|
|
50
|
+
# Read compressed result
|
|
51
|
+
return output_file.read_bytes()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def decompress(data: bytes) -> bytes:
|
|
55
|
+
"""
|
|
56
|
+
Decompress a single data block, similar to zstd.decompress()
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
data: Compressed byte data
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Decompressed byte data
|
|
63
|
+
"""
|
|
64
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
65
|
+
tmpdir_path = Path(tmpdir)
|
|
66
|
+
|
|
67
|
+
# Write compressed data
|
|
68
|
+
input_file = tmpdir_path / "input.7z"
|
|
69
|
+
input_file.write_bytes(data)
|
|
70
|
+
|
|
71
|
+
# Decompress
|
|
72
|
+
output_dir = tmpdir_path / "output"
|
|
73
|
+
args = ["x", str(input_file), f"-o{output_dir}", "-y"]
|
|
74
|
+
|
|
75
|
+
run_7z(args)
|
|
76
|
+
|
|
77
|
+
# Find decompressed files
|
|
78
|
+
output_files = list(output_dir.glob("*"))
|
|
79
|
+
if not output_files:
|
|
80
|
+
raise ValueError("No files found in archive")
|
|
81
|
+
|
|
82
|
+
# Return first file's content
|
|
83
|
+
return output_files[0].read_bytes()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Compressor:
|
|
87
|
+
"""
|
|
88
|
+
Compressor class, provides an interface similar to zstd.ZstdCompressor
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, algorithm: str = "lzma2", level: int = 5):
|
|
92
|
+
self.algorithm = algorithm
|
|
93
|
+
self.level = level
|
|
94
|
+
|
|
95
|
+
def compress(self, data: Union[str, bytes]) -> bytes:
|
|
96
|
+
"""Compress data"""
|
|
97
|
+
return compress(data, self.algorithm, self.level)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Decompressor:
|
|
101
|
+
"""
|
|
102
|
+
Decompressor class, provides an interface similar to zstd.ZstdDecompressor
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def decompress(self, data: bytes) -> bytes:
|
|
106
|
+
"""Decompress data"""
|
|
107
|
+
return decompress(data)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Convenience functions, mimicking modern compression libraries
|
|
111
|
+
def lzma2_compress(data: Union[str, bytes], level: int = 5) -> bytes:
|
|
112
|
+
"""LZMA2 compression"""
|
|
113
|
+
return compress(data, "lzma2", level)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def lzma2_decompress(data: bytes) -> bytes:
|
|
117
|
+
"""LZMA2 decompression"""
|
|
118
|
+
return decompress(data)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def bzip2_compress(data: Union[str, bytes], level: int = 5) -> bytes:
|
|
122
|
+
"""BZIP2 compression"""
|
|
123
|
+
return compress(data, "bzip2", level)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def bzip2_decompress(data: bytes) -> bytes:
|
|
127
|
+
"""BZIP2 decompression"""
|
|
128
|
+
return decompress(data)
|
py7zz/config.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration and Preset System
|
|
3
|
+
|
|
4
|
+
Provides advanced configuration options and preset configurations
|
|
5
|
+
for different use cases.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Config:
|
|
14
|
+
"""
|
|
15
|
+
Advanced configuration for 7z operations.
|
|
16
|
+
|
|
17
|
+
This class allows fine-grained control over compression parameters.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Compression settings
|
|
21
|
+
compression: str = "lzma2" # lzma2, lzma, ppmd, bzip2, deflate
|
|
22
|
+
level: int = 5 # 0-9, higher = better compression
|
|
23
|
+
solid: bool = True # Solid archive (better compression)
|
|
24
|
+
|
|
25
|
+
# Performance settings
|
|
26
|
+
threads: Optional[int] = None # Number of threads (None = auto)
|
|
27
|
+
memory_limit: Optional[str] = None # Memory limit (e.g., "1g", "512m")
|
|
28
|
+
|
|
29
|
+
# Security settings
|
|
30
|
+
password: Optional[str] = None # Archive password
|
|
31
|
+
encrypt_filenames: bool = False # Encrypt file names
|
|
32
|
+
|
|
33
|
+
# Advanced options
|
|
34
|
+
dictionary_size: Optional[str] = None # Dictionary size (e.g., "32m")
|
|
35
|
+
word_size: Optional[int] = None # Word size for LZMA
|
|
36
|
+
fast_bytes: Optional[int] = None # Fast bytes for LZMA
|
|
37
|
+
|
|
38
|
+
def to_7z_args(self) -> List[str]:
|
|
39
|
+
"""Convert config to 7z command line arguments."""
|
|
40
|
+
args = []
|
|
41
|
+
|
|
42
|
+
# Compression level
|
|
43
|
+
args.append(f"-mx{self.level}")
|
|
44
|
+
|
|
45
|
+
# Compression method
|
|
46
|
+
args.append(f"-m0={self.compression}")
|
|
47
|
+
|
|
48
|
+
# Solid archive
|
|
49
|
+
if not self.solid:
|
|
50
|
+
args.append("-ms=off")
|
|
51
|
+
|
|
52
|
+
# Threads
|
|
53
|
+
if self.threads is not None:
|
|
54
|
+
args.append(f"-mmt{self.threads}")
|
|
55
|
+
|
|
56
|
+
# Memory limit
|
|
57
|
+
if self.memory_limit:
|
|
58
|
+
args.append(f"-mmemuse={self.memory_limit}")
|
|
59
|
+
|
|
60
|
+
# Dictionary size
|
|
61
|
+
if self.dictionary_size:
|
|
62
|
+
args.append(f"-md={self.dictionary_size}")
|
|
63
|
+
|
|
64
|
+
# Word size
|
|
65
|
+
if self.word_size:
|
|
66
|
+
args.append(f"-mfb={self.word_size}")
|
|
67
|
+
|
|
68
|
+
# Fast bytes
|
|
69
|
+
if self.fast_bytes:
|
|
70
|
+
args.append(f"-mfb={self.fast_bytes}")
|
|
71
|
+
|
|
72
|
+
# Password
|
|
73
|
+
if self.password:
|
|
74
|
+
args.append(f"-p{self.password}")
|
|
75
|
+
|
|
76
|
+
# Encrypt filenames
|
|
77
|
+
if self.encrypt_filenames and self.password:
|
|
78
|
+
args.append("-mhe")
|
|
79
|
+
|
|
80
|
+
return args
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Presets:
|
|
84
|
+
"""
|
|
85
|
+
Predefined configurations for common use cases.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def fast() -> Config:
|
|
90
|
+
"""
|
|
91
|
+
Fast compression preset.
|
|
92
|
+
|
|
93
|
+
Optimized for speed over compression ratio.
|
|
94
|
+
Good for temporary files or when time is critical.
|
|
95
|
+
"""
|
|
96
|
+
return Config(
|
|
97
|
+
compression="lzma2",
|
|
98
|
+
level=1,
|
|
99
|
+
solid=False,
|
|
100
|
+
threads=None, # Use all available threads
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def balanced() -> Config:
|
|
105
|
+
"""
|
|
106
|
+
Balanced preset (default).
|
|
107
|
+
|
|
108
|
+
Good balance between compression ratio and speed.
|
|
109
|
+
Suitable for most general-purpose compression tasks.
|
|
110
|
+
"""
|
|
111
|
+
return Config(
|
|
112
|
+
compression="lzma2",
|
|
113
|
+
level=5,
|
|
114
|
+
solid=True,
|
|
115
|
+
threads=None,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def backup() -> Config:
|
|
120
|
+
"""
|
|
121
|
+
Backup preset.
|
|
122
|
+
|
|
123
|
+
Optimized for maximum compression ratio.
|
|
124
|
+
Good for long-term storage where space matters more than time.
|
|
125
|
+
"""
|
|
126
|
+
return Config(
|
|
127
|
+
compression="lzma2",
|
|
128
|
+
level=7,
|
|
129
|
+
solid=True,
|
|
130
|
+
dictionary_size="64m",
|
|
131
|
+
threads=None,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def ultra() -> Config:
|
|
136
|
+
"""
|
|
137
|
+
Ultra compression preset.
|
|
138
|
+
|
|
139
|
+
Maximum compression ratio at the cost of speed.
|
|
140
|
+
Use when storage space is extremely limited.
|
|
141
|
+
"""
|
|
142
|
+
return Config(
|
|
143
|
+
compression="lzma2",
|
|
144
|
+
level=9,
|
|
145
|
+
solid=True,
|
|
146
|
+
dictionary_size="128m",
|
|
147
|
+
word_size=64,
|
|
148
|
+
fast_bytes=64,
|
|
149
|
+
threads=1, # Single thread for maximum compression
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def secure() -> Config:
|
|
154
|
+
"""
|
|
155
|
+
Secure preset with encryption.
|
|
156
|
+
|
|
157
|
+
Balanced compression with password protection.
|
|
158
|
+
Note: Password must be set separately.
|
|
159
|
+
"""
|
|
160
|
+
return Config(
|
|
161
|
+
compression="lzma2",
|
|
162
|
+
level=5,
|
|
163
|
+
solid=True,
|
|
164
|
+
encrypt_filenames=True,
|
|
165
|
+
# password must be set by user
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def compatibility() -> Config:
|
|
170
|
+
"""
|
|
171
|
+
Compatibility preset.
|
|
172
|
+
|
|
173
|
+
Uses widely supported compression methods.
|
|
174
|
+
Good for archives that need to be opened on older systems.
|
|
175
|
+
"""
|
|
176
|
+
return Config(
|
|
177
|
+
compression="deflate",
|
|
178
|
+
level=6,
|
|
179
|
+
solid=False,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def get_preset(cls, name: str) -> Config:
|
|
184
|
+
"""
|
|
185
|
+
Get a preset configuration by name.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
name: Preset name ("fast", "balanced", "backup", "ultra", "secure", "compatibility")
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Config object for the specified preset
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If preset name is not recognized
|
|
195
|
+
"""
|
|
196
|
+
presets = {
|
|
197
|
+
"fast": cls.fast,
|
|
198
|
+
"balanced": cls.balanced,
|
|
199
|
+
"backup": cls.backup,
|
|
200
|
+
"ultra": cls.ultra,
|
|
201
|
+
"secure": cls.secure,
|
|
202
|
+
"compatibility": cls.compatibility,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if name not in presets:
|
|
206
|
+
available = ", ".join(presets.keys())
|
|
207
|
+
raise ValueError(f"Unknown preset '{name}'. Available presets: {available}")
|
|
208
|
+
|
|
209
|
+
return presets[name]()
|
|
210
|
+
|
|
211
|
+
@classmethod
|
|
212
|
+
def list_presets(cls) -> List[str]:
|
|
213
|
+
"""List all available preset names."""
|
|
214
|
+
return ["fast", "balanced", "backup", "ultra", "secure", "compatibility"]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def create_custom_config(**kwargs: Any) -> Config:
|
|
218
|
+
"""
|
|
219
|
+
Create a custom configuration with specified parameters.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
**kwargs: Any Config parameters to override
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Config object with specified parameters
|
|
226
|
+
|
|
227
|
+
Example:
|
|
228
|
+
>>> config = create_custom_config(level=9, threads=4, password="secret")
|
|
229
|
+
>>> # Use with SevenZipFile or create_archive
|
|
230
|
+
"""
|
|
231
|
+
return Config(**kwargs)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def get_recommended_preset(purpose: str) -> Config:
|
|
235
|
+
"""
|
|
236
|
+
Get recommended preset based on intended purpose.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
purpose: Intended use ("temp", "backup", "distribution", "secure", "fast")
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Recommended Config object
|
|
243
|
+
"""
|
|
244
|
+
recommendations = {
|
|
245
|
+
"temp": Presets.fast(),
|
|
246
|
+
"temporary": Presets.fast(),
|
|
247
|
+
"backup": Presets.backup(),
|
|
248
|
+
"archive": Presets.backup(),
|
|
249
|
+
"distribution": Presets.balanced(),
|
|
250
|
+
"share": Presets.balanced(),
|
|
251
|
+
"secure": Presets.secure(),
|
|
252
|
+
"encrypted": Presets.secure(),
|
|
253
|
+
"fast": Presets.fast(),
|
|
254
|
+
"quick": Presets.fast(),
|
|
255
|
+
"max": Presets.ultra(),
|
|
256
|
+
"maximum": Presets.ultra(),
|
|
257
|
+
"ultra": Presets.ultra(),
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return recommendations.get(purpose.lower(), Presets.balanced())
|