py7zz 0.1.0__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 ADDED
@@ -0,0 +1,205 @@
1
+ """
2
+ py7zz - Python wrapper for 7zz CLI tool
3
+
4
+ Provides a consistent OOP interface across platforms (macOS, Linux, Windows)
5
+ with automatic update mechanisms.
6
+ """
7
+
8
+ # Configuration and Presets
9
+ # Bundled information
10
+ from .bundled_info import (
11
+ get_bundled_7zz_version,
12
+ get_release_type,
13
+ get_version_info,
14
+ is_auto_release,
15
+ is_dev_release,
16
+ is_stable_release,
17
+ )
18
+ from .config import Config, Presets, create_custom_config, get_recommended_preset
19
+ from .core import SevenZipFile, run_7z
20
+
21
+ # Exceptions
22
+ from .exceptions import (
23
+ ArchiveNotFoundError,
24
+ BinaryNotFoundError,
25
+ CompressionError,
26
+ ConfigurationError,
27
+ CorruptedArchiveError,
28
+ ExtractionError,
29
+ FileNotFoundError,
30
+ InsufficientSpaceError,
31
+ InvalidPasswordError,
32
+ OperationTimeoutError,
33
+ PasswordRequiredError,
34
+ Py7zzError,
35
+ UnsupportedFormatError,
36
+ )
37
+
38
+ # Layer 1: Simple Function API
39
+ from .simple import (
40
+ compress_directory,
41
+ compress_file,
42
+ create_archive,
43
+ extract_archive,
44
+ get_archive_info,
45
+ list_archive,
46
+ test_archive,
47
+ )
48
+
49
+ # Version information
50
+ from .version import (
51
+ generate_auto_version,
52
+ generate_dev_version,
53
+ get_base_version,
54
+ get_build_number,
55
+ get_version,
56
+ get_version_type,
57
+ is_auto_version,
58
+ is_dev_version,
59
+ is_stable_version,
60
+ parse_version,
61
+ )
62
+
63
+ # Try to get dynamic version, fallback to hardcoded if needed
64
+ try:
65
+ __version__ = get_version()
66
+ except Exception:
67
+ # Fallback to hardcoded version if dynamic version fails
68
+ from .version import __version__
69
+ from .version import (
70
+ get_version_info as get_legacy_version_info,
71
+ )
72
+
73
+ # Import async simple functions if available
74
+ try:
75
+ from .simple import (
76
+ compress_directory_async, # noqa: F401
77
+ compress_file_async, # noqa: F401
78
+ create_archive_async, # noqa: F401
79
+ extract_archive_async, # noqa: F401
80
+ )
81
+
82
+ _simple_async_available = True
83
+ except ImportError:
84
+ _simple_async_available = False
85
+
86
+ # Optional compression algorithm interface
87
+ try:
88
+ from .compression import (
89
+ Compressor, # noqa: F401
90
+ Decompressor, # noqa: F401
91
+ bzip2_compress, # noqa: F401
92
+ bzip2_decompress, # noqa: F401
93
+ compress, # noqa: F401
94
+ decompress, # noqa: F401
95
+ lzma2_compress, # noqa: F401
96
+ lzma2_decompress, # noqa: F401
97
+ )
98
+
99
+ _compression_available = True
100
+ except ImportError:
101
+ _compression_available = False
102
+
103
+ # Optional async operations interface
104
+ try:
105
+ from .async_ops import (
106
+ AsyncSevenZipFile, # noqa: F401
107
+ ProgressInfo, # noqa: F401
108
+ batch_compress_async, # noqa: F401
109
+ batch_extract_async, # noqa: F401
110
+ compress_async, # noqa: F401
111
+ extract_async, # noqa: F401
112
+ )
113
+
114
+ _async_available = True
115
+ except ImportError:
116
+ _async_available = False
117
+
118
+ # Build __all__ list based on available modules
119
+ __all__ = [
120
+ # Core API (Layer 2)
121
+ "SevenZipFile",
122
+ "run_7z",
123
+ # Version information
124
+ "__version__",
125
+ "get_version",
126
+ "get_version_info",
127
+ "get_legacy_version_info",
128
+ "parse_version",
129
+ "generate_auto_version",
130
+ "generate_dev_version",
131
+ "get_version_type",
132
+ "is_auto_version",
133
+ "is_dev_version",
134
+ "is_stable_version",
135
+ "get_base_version",
136
+ "get_build_number",
137
+ # Bundled information
138
+ "get_bundled_7zz_version",
139
+ "get_release_type",
140
+ "is_stable_release",
141
+ "is_auto_release",
142
+ "is_dev_release",
143
+ # Simple API (Layer 1)
144
+ "create_archive",
145
+ "extract_archive",
146
+ "list_archive",
147
+ "compress_file",
148
+ "compress_directory",
149
+ "get_archive_info",
150
+ "test_archive",
151
+ # Configuration
152
+ "Config",
153
+ "Presets",
154
+ "create_custom_config",
155
+ "get_recommended_preset",
156
+ # Exceptions
157
+ "Py7zzError",
158
+ "FileNotFoundError",
159
+ "ArchiveNotFoundError",
160
+ "CompressionError",
161
+ "ExtractionError",
162
+ "CorruptedArchiveError",
163
+ "UnsupportedFormatError",
164
+ "PasswordRequiredError",
165
+ "InvalidPasswordError",
166
+ "BinaryNotFoundError",
167
+ "InsufficientSpaceError",
168
+ "ConfigurationError",
169
+ "OperationTimeoutError",
170
+ ]
171
+
172
+ # Add compression API if available
173
+ if _compression_available:
174
+ __all__.extend(
175
+ [
176
+ "compress",
177
+ "decompress",
178
+ "Compressor",
179
+ "Decompressor",
180
+ "lzma2_compress",
181
+ "lzma2_decompress",
182
+ "bzip2_compress",
183
+ "bzip2_decompress",
184
+ ]
185
+ )
186
+
187
+ # Add async API if available
188
+ if _async_available:
189
+ __all__.extend(
190
+ [
191
+ "AsyncSevenZipFile",
192
+ "ProgressInfo",
193
+ "compress_async",
194
+ "extract_async",
195
+ "batch_compress_async",
196
+ "batch_extract_async",
197
+ ]
198
+ )
199
+
200
+ # Add async simple API if available
201
+ if _simple_async_available:
202
+ __all__.extend(["create_archive_async", "extract_archive_async", "compress_file_async", "compress_directory_async"])
203
+
204
+ # Version is now managed centrally in version.py
205
+ # __version__ is imported from .version at the top of this file
py7zz/async_ops.py ADDED
@@ -0,0 +1,323 @@
1
+ """
2
+ Asynchronous operations for py7zz package.
3
+
4
+ Provides async support for compression and extraction operations with progress reporting.
5
+ This module implements M4 milestone features for py7zz.
6
+ """
7
+
8
+ import asyncio
9
+ import subprocess
10
+ from pathlib import Path
11
+ from typing import Callable, List, Optional, Union
12
+
13
+ from .core import find_7z_binary
14
+ from .exceptions import FileNotFoundError
15
+
16
+
17
+ class ProgressInfo:
18
+ """Progress information for async operations."""
19
+
20
+ def __init__(
21
+ self,
22
+ operation: str,
23
+ current_file: str = "",
24
+ files_processed: int = 0,
25
+ total_files: int = 0,
26
+ bytes_processed: int = 0,
27
+ total_bytes: int = 0,
28
+ percentage: float = 0.0,
29
+ ) -> None:
30
+ self.operation = operation
31
+ self.current_file = current_file
32
+ self.files_processed = files_processed
33
+ self.total_files = total_files
34
+ self.bytes_processed = bytes_processed
35
+ self.total_bytes = total_bytes
36
+ self.percentage = percentage
37
+
38
+ def __repr__(self) -> str:
39
+ return (
40
+ f"ProgressInfo(operation='{self.operation}', "
41
+ f"current_file='{self.current_file}', "
42
+ f"files_processed={self.files_processed}, "
43
+ f"total_files={self.total_files}, "
44
+ f"percentage={self.percentage:.1f}%)"
45
+ )
46
+
47
+
48
+ class AsyncSevenZipFile:
49
+ """
50
+ Async wrapper for SevenZipFile operations.
51
+
52
+ Provides asynchronous compression and extraction with progress reporting.
53
+ """
54
+
55
+ def __init__(self, file: Union[str, Path], mode: str = "r"):
56
+ """
57
+ Initialize AsyncSevenZipFile.
58
+
59
+ Args:
60
+ file: Path to the archive file
61
+ mode: File mode ('r' for read, 'w' for write)
62
+ """
63
+ self.file = Path(file)
64
+ self.mode = mode
65
+ self._validate_mode()
66
+
67
+ def _validate_mode(self) -> None:
68
+ """Validate file mode."""
69
+ if self.mode not in ("r", "w"):
70
+ raise ValueError(f"Invalid mode: {self.mode}")
71
+
72
+ async def __aenter__(self) -> "AsyncSevenZipFile":
73
+ """Async context manager entry."""
74
+ return self
75
+
76
+ async def __aexit__(
77
+ self, exc_type: Optional[type], exc_val: Optional[BaseException], exc_tb: Optional[object]
78
+ ) -> None:
79
+ """Async context manager exit."""
80
+ pass
81
+
82
+ async def add_async(
83
+ self, name: Union[str, Path], progress_callback: Optional[Callable[[ProgressInfo], None]] = None
84
+ ) -> None:
85
+ """
86
+ Add file or directory to archive asynchronously.
87
+
88
+ Args:
89
+ name: Path to file or directory to add
90
+ progress_callback: Optional callback for progress updates
91
+ """
92
+ if self.mode == "r":
93
+ raise ValueError("Cannot add to archive opened in read mode")
94
+
95
+ name = Path(name)
96
+ if not name.exists():
97
+ raise FileNotFoundError(f"File not found: {name}")
98
+
99
+ # Build 7z command
100
+ binary = find_7z_binary()
101
+ args = [binary, "a", str(self.file), str(name)]
102
+
103
+ try:
104
+ await self._run_with_progress(args, operation="compress", progress_callback=progress_callback)
105
+ except subprocess.CalledProcessError as e:
106
+ raise RuntimeError(f"Failed to add {name} to archive: {e.stderr}") from e
107
+
108
+ async def extract_async(
109
+ self,
110
+ path: Union[str, Path] = ".",
111
+ overwrite: bool = False,
112
+ progress_callback: Optional[Callable[[ProgressInfo], None]] = None,
113
+ ) -> None:
114
+ """
115
+ Extract archive contents asynchronously.
116
+
117
+ Args:
118
+ path: Directory to extract to
119
+ overwrite: Whether to overwrite existing files
120
+ progress_callback: Optional callback for progress updates
121
+ """
122
+ if self.mode == "w":
123
+ raise ValueError("Cannot extract from archive opened in write mode")
124
+
125
+ if not self.file.exists():
126
+ raise FileNotFoundError(f"Archive not found: {self.file}")
127
+
128
+ path = Path(path)
129
+ path.mkdir(parents=True, exist_ok=True)
130
+
131
+ binary = find_7z_binary()
132
+ args = [binary, "x", str(self.file), f"-o{path}"]
133
+
134
+ if overwrite:
135
+ args.append("-y")
136
+
137
+ try:
138
+ await self._run_with_progress(args, operation="extract", progress_callback=progress_callback)
139
+ except subprocess.CalledProcessError as e:
140
+ raise RuntimeError(f"Failed to extract archive: {e.stderr}") from e
141
+
142
+ async def _run_with_progress(
143
+ self, args: List[str], operation: str, progress_callback: Optional[Callable[[ProgressInfo], None]] = None
144
+ ) -> None:
145
+ """
146
+ Run 7z command with progress monitoring.
147
+
148
+ Args:
149
+ args: Command arguments
150
+ operation: Operation type ('compress' or 'extract')
151
+ progress_callback: Optional callback for progress updates
152
+ """
153
+ process = await asyncio.create_subprocess_exec(
154
+ *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
155
+ )
156
+
157
+ try:
158
+ if progress_callback:
159
+ # Monitor progress in separate task
160
+ progress_task = asyncio.create_task(self._monitor_progress(process, operation, progress_callback))
161
+ await progress_task
162
+ # Wait for process completion
163
+ await process.wait()
164
+ stdout, stderr = b"", b""
165
+ else:
166
+ stdout, stderr = await process.communicate()
167
+
168
+ if process.returncode != 0:
169
+ raise subprocess.CalledProcessError(process.returncode or -1, args, stdout, stderr)
170
+
171
+ except asyncio.CancelledError:
172
+ if process.returncode is None:
173
+ process.terminate()
174
+ await process.wait()
175
+ raise
176
+
177
+ async def _monitor_progress(
178
+ self, process: asyncio.subprocess.Process, operation: str, progress_callback: Callable[[ProgressInfo], None]
179
+ ) -> None:
180
+ """
181
+ Monitor subprocess progress and call callback.
182
+
183
+ Args:
184
+ process: Subprocess to monitor
185
+ operation: Operation type
186
+ progress_callback: Callback for progress updates
187
+ """
188
+ files_processed = 0
189
+ current_file = ""
190
+
191
+ if process.stdout is None:
192
+ return
193
+
194
+ async for line_bytes in process.stdout:
195
+ line = line_bytes.decode("utf-8", errors="replace").strip()
196
+
197
+ # Parse 7z output for progress information
198
+ if "Compressing" in line or "Extracting" in line:
199
+ # Extract filename from progress line
200
+ if " " in line:
201
+ current_file = line.split(" ")[-1]
202
+ files_processed += 1
203
+
204
+ # Calculate approximate progress
205
+ # Note: This is simplified - actual 7z progress parsing is more complex
206
+ progress = ProgressInfo(
207
+ operation=operation,
208
+ current_file=current_file,
209
+ files_processed=files_processed,
210
+ total_files=max(1, files_processed), # Placeholder
211
+ percentage=min(100.0, files_processed * 10.0), # Simplified calculation
212
+ )
213
+
214
+ progress_callback(progress)
215
+
216
+ # Small delay to prevent callback spam
217
+ await asyncio.sleep(0.01)
218
+
219
+
220
+ async def compress_async(
221
+ archive_path: Union[str, Path],
222
+ files: List[Union[str, Path]],
223
+ progress_callback: Optional[Callable[[ProgressInfo], None]] = None,
224
+ ) -> None:
225
+ """
226
+ Compress files asynchronously with progress reporting.
227
+
228
+ Args:
229
+ archive_path: Path to the archive to create
230
+ files: List of files/directories to add
231
+ progress_callback: Optional callback for progress updates
232
+
233
+ Example:
234
+ >>> async def progress_handler(info):
235
+ ... print(f"Progress: {info.percentage:.1f}% - {info.current_file}")
236
+ >>> await py7zz.compress_async("backup.7z", ["documents/"], progress_handler)
237
+ """
238
+ async with AsyncSevenZipFile(archive_path, "w") as sz:
239
+ for file_path in files:
240
+ path = Path(file_path)
241
+ if path.exists():
242
+ await sz.add_async(file_path, progress_callback)
243
+ else:
244
+ raise FileNotFoundError(f"File or directory not found: {file_path}")
245
+
246
+
247
+ async def extract_async(
248
+ archive_path: Union[str, Path],
249
+ output_dir: Union[str, Path] = ".",
250
+ overwrite: bool = True,
251
+ progress_callback: Optional[Callable[[ProgressInfo], None]] = None,
252
+ ) -> None:
253
+ """
254
+ Extract archive asynchronously with progress reporting.
255
+
256
+ Args:
257
+ archive_path: Path to the archive to extract
258
+ output_dir: Directory to extract files to
259
+ overwrite: Whether to overwrite existing files
260
+ progress_callback: Optional callback for progress updates
261
+
262
+ Example:
263
+ >>> async def progress_handler(info):
264
+ ... print(f"Extracting: {info.current_file}")
265
+ >>> await py7zz.extract_async("backup.7z", "extracted/", progress_handler)
266
+ """
267
+ if not Path(archive_path).exists():
268
+ raise FileNotFoundError(f"Archive not found: {archive_path}")
269
+
270
+ async with AsyncSevenZipFile(archive_path, "r") as sz:
271
+ await sz.extract_async(output_dir, overwrite, progress_callback)
272
+
273
+
274
+ async def batch_compress_async(
275
+ operations: List[tuple], progress_callback: Optional[Callable[[ProgressInfo], None]] = None
276
+ ) -> None:
277
+ """
278
+ Perform multiple compression operations concurrently.
279
+
280
+ Args:
281
+ operations: List of (archive_path, files) tuples
282
+ progress_callback: Optional callback for progress updates
283
+
284
+ Example:
285
+ >>> operations = [
286
+ ... ("backup1.7z", ["documents/"]),
287
+ ... ("backup2.7z", ["photos/"]),
288
+ ... ]
289
+ >>> await py7zz.batch_compress_async(operations)
290
+ """
291
+ tasks = []
292
+
293
+ for archive_path, files in operations:
294
+ task = compress_async(archive_path, files, progress_callback)
295
+ tasks.append(task)
296
+
297
+ await asyncio.gather(*tasks)
298
+
299
+
300
+ async def batch_extract_async(
301
+ operations: List[tuple], progress_callback: Optional[Callable[[ProgressInfo], None]] = None
302
+ ) -> None:
303
+ """
304
+ Perform multiple extraction operations concurrently.
305
+
306
+ Args:
307
+ operations: List of (archive_path, output_dir) tuples
308
+ progress_callback: Optional callback for progress updates
309
+
310
+ Example:
311
+ >>> operations = [
312
+ ... ("backup1.7z", "extracted1/"),
313
+ ... ("backup2.7z", "extracted2/"),
314
+ ... ]
315
+ >>> await py7zz.batch_extract_async(operations)
316
+ """
317
+ tasks = []
318
+
319
+ for archive_path, output_dir in operations:
320
+ task = extract_async(archive_path, output_dir, progress_callback=progress_callback)
321
+ tasks.append(task)
322
+
323
+ await asyncio.gather(*tasks)
py7zz/bin/7z.dll ADDED
Binary file
py7zz/bin/7zz.exe ADDED
Binary file
py7zz/bundled_info.py ADDED
@@ -0,0 +1,122 @@
1
+ """
2
+ Bundled version information for py7zz package.
3
+
4
+ This module provides version registry and bundled information for the py7zz package,
5
+ supporting the new PEP 440 compliant version system.
6
+ """
7
+
8
+ from typing import Dict, Union
9
+
10
+ from .version import __version__
11
+
12
+ # Version registry containing all version information
13
+ VERSION_REGISTRY: Dict[str, Dict[str, Union[str, None]]] = {
14
+ "0.1.0": {
15
+ "7zz_version": "25.00",
16
+ "release_date": "2024-12-15",
17
+ "release_type": "stable",
18
+ "github_tag": "v0.1.0",
19
+ "changelog_url": "https://github.com/rxchi1d/py7zz/releases/tag/v0.1.0",
20
+ },
21
+ # Auto versions will be dynamically added by CI/CD
22
+ # Example: "0.1.0a1": {"7zz_version": "24.08", ...}
23
+ # Dev versions will be manually added
24
+ # Example: "0.2.0.dev1": {"7zz_version": "24.08", ...}
25
+ }
26
+
27
+
28
+ def get_version_info() -> Dict[str, Union[str, None]]:
29
+ """
30
+ Get detailed version information for the current py7zz version.
31
+
32
+ Returns:
33
+ Dictionary containing version information including:
34
+ - py7zz_version: Current py7zz version
35
+ - bundled_7zz_version: Bundled 7zz version
36
+ - release_type: Type of release (stable, auto, dev)
37
+ - release_date: Release date
38
+ - github_tag: GitHub tag
39
+ - changelog_url: URL to changelog
40
+
41
+ Example:
42
+ >>> get_version_info()
43
+ {
44
+ 'py7zz_version': '0.1.0',
45
+ 'bundled_7zz_version': '24.07',
46
+ 'release_type': 'stable',
47
+ 'release_date': '2024-12-15',
48
+ 'github_tag': 'v0.1.0',
49
+ 'changelog_url': 'https://github.com/rxchi1d/py7zz/releases/tag/v0.1.0'
50
+ }
51
+ """
52
+ current_version = __version__
53
+ info = VERSION_REGISTRY.get(current_version, {})
54
+
55
+ return {
56
+ "py7zz_version": current_version,
57
+ "bundled_7zz_version": info.get("7zz_version", "unknown"),
58
+ "release_type": info.get("release_type", "unknown"),
59
+ "release_date": info.get("release_date", "unknown"),
60
+ "github_tag": info.get("github_tag", f"v{current_version}"),
61
+ "changelog_url": info.get("changelog_url", f"https://github.com/rxchi1d/py7zz/releases/tag/v{current_version}"),
62
+ }
63
+
64
+
65
+ def get_bundled_7zz_version() -> str:
66
+ """
67
+ Get the bundled 7zz version for the current py7zz version.
68
+
69
+ Returns:
70
+ Bundled 7zz version string
71
+
72
+ Example:
73
+ >>> get_bundled_7zz_version()
74
+ '24.07'
75
+ """
76
+ version = get_version_info()["bundled_7zz_version"]
77
+ return version if isinstance(version, str) else "unknown"
78
+
79
+
80
+ def get_release_type() -> str:
81
+ """
82
+ Get the release type for the current py7zz version.
83
+
84
+ Returns:
85
+ Release type: 'stable', 'auto', or 'dev'
86
+
87
+ Example:
88
+ >>> get_release_type()
89
+ 'stable'
90
+ """
91
+ release_type = get_version_info()["release_type"]
92
+ return release_type if isinstance(release_type, str) else "unknown"
93
+
94
+
95
+ def is_stable_release() -> bool:
96
+ """
97
+ Check if the current version is a stable release.
98
+
99
+ Returns:
100
+ True if current version is stable
101
+ """
102
+ return get_release_type() == "stable"
103
+
104
+
105
+ def is_auto_release() -> bool:
106
+ """
107
+ Check if the current version is an auto release.
108
+
109
+ Returns:
110
+ True if current version is auto
111
+ """
112
+ return get_release_type() == "auto"
113
+
114
+
115
+ def is_dev_release() -> bool:
116
+ """
117
+ Check if the current version is a dev release.
118
+
119
+ Returns:
120
+ True if current version is dev
121
+ """
122
+ return get_release_type() == "dev"