provide-foundation 0.0.0.dev1__py3-none-any.whl → 0.0.0.dev3__py3-none-any.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.
- provide/foundation/__init__.py +36 -10
- provide/foundation/archive/__init__.py +1 -1
- provide/foundation/archive/base.py +15 -14
- provide/foundation/archive/bzip2.py +40 -40
- provide/foundation/archive/gzip.py +42 -42
- provide/foundation/archive/operations.py +93 -96
- provide/foundation/archive/tar.py +33 -31
- provide/foundation/archive/zip.py +52 -50
- provide/foundation/asynctools/__init__.py +20 -0
- provide/foundation/asynctools/core.py +126 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +15 -9
- provide/foundation/cli/commands/logs/__init__.py +3 -3
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +4 -4
- provide/foundation/cli/commands/logs/send.py +3 -3
- provide/foundation/cli/commands/logs/tail.py +3 -3
- provide/foundation/cli/decorators.py +11 -11
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -40
- provide/foundation/cli/utils.py +21 -18
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +477 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +6 -20
- provide/foundation/config/loader.py +10 -4
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +36 -14
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +88 -110
- provide/foundation/crypto/certificates/__init__.py +9 -5
- provide/foundation/crypto/certificates/base.py +2 -2
- provide/foundation/crypto/certificates/certificate.py +48 -19
- provide/foundation/crypto/certificates/factory.py +26 -18
- provide/foundation/crypto/certificates/generator.py +24 -23
- provide/foundation/crypto/certificates/loader.py +24 -16
- provide/foundation/crypto/certificates/operations.py +17 -10
- provide/foundation/crypto/certificates/trust.py +21 -21
- provide/foundation/env/__init__.py +28 -0
- provide/foundation/env/core.py +218 -0
- provide/foundation/errors/__init__.py +3 -3
- provide/foundation/errors/decorators.py +0 -234
- provide/foundation/errors/types.py +0 -98
- provide/foundation/eventsets/display.py +13 -14
- provide/foundation/eventsets/registry.py +61 -31
- provide/foundation/eventsets/resolver.py +50 -46
- provide/foundation/eventsets/sets/das.py +8 -8
- provide/foundation/eventsets/sets/database.py +14 -14
- provide/foundation/eventsets/sets/http.py +21 -21
- provide/foundation/eventsets/sets/llm.py +16 -16
- provide/foundation/eventsets/sets/task_queue.py +13 -13
- provide/foundation/eventsets/types.py +7 -7
- provide/foundation/file/directory.py +14 -23
- provide/foundation/file/lock.py +4 -3
- provide/foundation/hub/components.py +75 -389
- provide/foundation/hub/config.py +157 -0
- provide/foundation/hub/discovery.py +63 -0
- provide/foundation/hub/handlers.py +89 -0
- provide/foundation/hub/lifecycle.py +195 -0
- provide/foundation/hub/manager.py +7 -4
- provide/foundation/hub/processors.py +49 -0
- provide/foundation/integrations/__init__.py +11 -0
- provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
- provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/client.py +14 -14
- provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
- provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +69 -299
- provide/foundation/logger/config/telemetry.py +39 -121
- provide/foundation/logger/factories.py +2 -2
- provide/foundation/logger/processors/main.py +12 -10
- provide/foundation/logger/ratelimit/limiters.py +4 -4
- provide/foundation/logger/ratelimit/processor.py +1 -1
- provide/foundation/logger/setup/coordinator.py +39 -25
- provide/foundation/logger/setup/processors.py +3 -3
- provide/foundation/logger/setup/testing.py +14 -0
- provide/foundation/logger/trace.py +5 -5
- provide/foundation/metrics/__init__.py +1 -1
- provide/foundation/metrics/otel.py +3 -1
- provide/foundation/observability/__init__.py +3 -3
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +48 -0
- provide/foundation/process/lifecycle.py +69 -46
- provide/foundation/resilience/__init__.py +36 -0
- provide/foundation/resilience/circuit.py +166 -0
- provide/foundation/resilience/decorators.py +236 -0
- provide/foundation/resilience/fallback.py +208 -0
- provide/foundation/resilience/retry.py +327 -0
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +78 -0
- provide/foundation/streams/console.py +4 -5
- provide/foundation/streams/core.py +5 -2
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +29 -9
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +30 -20
- provide/foundation/testing/common/__init__.py +13 -15
- provide/foundation/testing/common/fixtures.py +27 -57
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +289 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +42 -516
- provide/foundation/testing/file/special_fixtures.py +145 -0
- provide/foundation/testing/logger.py +89 -8
- provide/foundation/testing/mocking/__init__.py +21 -21
- provide/foundation/testing/mocking/fixtures.py +80 -67
- provide/foundation/testing/process/__init__.py +23 -23
- provide/foundation/testing/process/async_fixtures.py +414 -0
- provide/foundation/testing/process/fixtures.py +48 -571
- provide/foundation/testing/process/subprocess_fixtures.py +210 -0
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +105 -0
- provide/foundation/testing/threading/data_fixtures.py +101 -0
- provide/foundation/testing/threading/execution_fixtures.py +278 -0
- provide/foundation/testing/threading/fixtures.py +32 -502
- provide/foundation/testing/threading/sync_fixtures.py +100 -0
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +95 -83
- provide/foundation/testing/transport/__init__.py +9 -9
- provide/foundation/testing/transport/fixtures.py +54 -54
- provide/foundation/time/__init__.py +18 -0
- provide/foundation/time/core.py +63 -0
- provide/foundation/tools/__init__.py +2 -2
- provide/foundation/tools/base.py +68 -67
- provide/foundation/tools/cache.py +69 -74
- provide/foundation/tools/downloader.py +68 -62
- provide/foundation/tools/installer.py +51 -57
- provide/foundation/tools/registry.py +38 -45
- provide/foundation/tools/resolver.py +70 -68
- provide/foundation/tools/verifier.py +39 -50
- provide/foundation/tracer/spans.py +2 -14
- provide/foundation/transport/__init__.py +26 -33
- provide/foundation/transport/base.py +32 -30
- provide/foundation/transport/client.py +44 -49
- provide/foundation/transport/config.py +36 -107
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +113 -114
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +17 -14
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
- provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
- provide_foundation-0.0.0.dev1.dist-info/RECORD +0 -200
- /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
- /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
provide/foundation/__init__.py
CHANGED
@@ -9,12 +9,12 @@ Primary public interface for the library, re-exporting common components.
|
|
9
9
|
# Export config module for easy access
|
10
10
|
# New foundation components
|
11
11
|
# Make the errors module available for detailed imports
|
12
|
-
from provide.foundation import config, errors, platform, process
|
12
|
+
from provide.foundation import config, errors, platform, process, resilience
|
13
13
|
from provide.foundation._version import __version__
|
14
14
|
|
15
15
|
# Console I/O functions (always available - handles click dependency internally)
|
16
16
|
from provide.foundation.console import perr, pin, pout
|
17
|
-
from provide.foundation.context import Context
|
17
|
+
from provide.foundation.context import CLIContext, Context
|
18
18
|
|
19
19
|
# Error handling exports - only the essentials
|
20
20
|
from provide.foundation.errors import (
|
@@ -27,6 +27,14 @@ from provide.foundation.errors import (
|
|
27
27
|
with_error_handling,
|
28
28
|
)
|
29
29
|
|
30
|
+
# Event set exports
|
31
|
+
from provide.foundation.eventsets.display import show_event_matrix
|
32
|
+
from provide.foundation.eventsets.types import (
|
33
|
+
EventMapping,
|
34
|
+
EventSet,
|
35
|
+
FieldMapping,
|
36
|
+
)
|
37
|
+
|
30
38
|
# Hub and Registry exports (public API)
|
31
39
|
from provide.foundation.hub.components import ComponentCategory, get_component_registry
|
32
40
|
from provide.foundation.hub.manager import Hub, clear_hub, get_hub
|
@@ -39,12 +47,17 @@ from provide.foundation.logger import (
|
|
39
47
|
setup_logging, # Setup function (backward compatibility)
|
40
48
|
)
|
41
49
|
|
42
|
-
#
|
43
|
-
from provide.foundation.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
50
|
+
# Resilience exports
|
51
|
+
from provide.foundation.resilience import (
|
52
|
+
BackoffStrategy,
|
53
|
+
CircuitBreaker,
|
54
|
+
CircuitState,
|
55
|
+
FallbackChain,
|
56
|
+
RetryExecutor,
|
57
|
+
RetryPolicy,
|
58
|
+
circuit_breaker,
|
59
|
+
fallback,
|
60
|
+
retry,
|
48
61
|
)
|
49
62
|
from provide.foundation.setup import (
|
50
63
|
setup_telemetry,
|
@@ -66,7 +79,7 @@ from provide.foundation.utils import (
|
|
66
79
|
|
67
80
|
|
68
81
|
# Lazy loading support for optional modules
|
69
|
-
def __getattr__(name: str):
|
82
|
+
def __getattr__(name: str) -> object:
|
70
83
|
"""Support lazy loading of optional modules."""
|
71
84
|
if name == "cli":
|
72
85
|
try:
|
@@ -92,7 +105,8 @@ __all__ = [
|
|
92
105
|
"show_event_matrix",
|
93
106
|
"ConsoleFormatterStr",
|
94
107
|
# New foundation modules
|
95
|
-
"
|
108
|
+
"CLIContext",
|
109
|
+
"Context", # Backward compatibility
|
96
110
|
# Event set types
|
97
111
|
"EventMapping",
|
98
112
|
"EventSet",
|
@@ -129,7 +143,19 @@ __all__ = [
|
|
129
143
|
"pout",
|
130
144
|
"platform",
|
131
145
|
"process",
|
146
|
+
# Resilience patterns
|
147
|
+
"retry",
|
148
|
+
"circuit_breaker",
|
149
|
+
"fallback",
|
150
|
+
"RetryPolicy",
|
151
|
+
"RetryExecutor",
|
152
|
+
"BackoffStrategy",
|
153
|
+
"CircuitBreaker",
|
154
|
+
"CircuitState",
|
155
|
+
"FallbackChain",
|
156
|
+
# Backward compatibility (deprecated)
|
132
157
|
"retry_on_error",
|
158
|
+
"resilience", # The resilience module for detailed imports
|
133
159
|
"setup_logging", # Backward compatibility
|
134
160
|
"setup_logger", # Consistent naming
|
135
161
|
"setup_telemetry",
|
@@ -8,63 +8,64 @@ from provide.foundation.errors import FoundationError
|
|
8
8
|
|
9
9
|
class ArchiveError(FoundationError):
|
10
10
|
"""Base exception for archive-related errors."""
|
11
|
+
|
11
12
|
pass
|
12
13
|
|
13
14
|
|
14
15
|
class BaseArchive(ABC):
|
15
16
|
"""
|
16
17
|
Abstract base class for all archive implementations.
|
17
|
-
|
18
|
+
|
18
19
|
This defines the common interface that all archive implementations
|
19
20
|
must follow, ensuring consistency across different archive formats.
|
20
21
|
"""
|
21
|
-
|
22
|
+
|
22
23
|
@abstractmethod
|
23
24
|
def create(self, source: Path, output: Path) -> Path:
|
24
25
|
"""
|
25
26
|
Create an archive from source path.
|
26
|
-
|
27
|
+
|
27
28
|
Args:
|
28
29
|
source: Source file or directory to archive
|
29
30
|
output: Output archive file path
|
30
|
-
|
31
|
+
|
31
32
|
Returns:
|
32
33
|
Path to the created archive file
|
33
|
-
|
34
|
+
|
34
35
|
Raises:
|
35
36
|
ArchiveError: If archive creation fails
|
36
37
|
"""
|
37
38
|
pass
|
38
|
-
|
39
|
+
|
39
40
|
@abstractmethod
|
40
41
|
def extract(self, archive: Path, output: Path) -> Path:
|
41
42
|
"""
|
42
43
|
Extract an archive to output path.
|
43
|
-
|
44
|
+
|
44
45
|
Args:
|
45
46
|
archive: Archive file to extract
|
46
47
|
output: Output directory for extracted contents
|
47
|
-
|
48
|
+
|
48
49
|
Returns:
|
49
50
|
Path to the extraction directory
|
50
|
-
|
51
|
+
|
51
52
|
Raises:
|
52
53
|
ArchiveError: If extraction fails
|
53
54
|
"""
|
54
55
|
pass
|
55
|
-
|
56
|
+
|
56
57
|
@abstractmethod
|
57
58
|
def validate(self, archive: Path) -> bool:
|
58
59
|
"""
|
59
60
|
Validate that an archive is properly formed.
|
60
|
-
|
61
|
+
|
61
62
|
Args:
|
62
63
|
archive: Archive file to validate
|
63
|
-
|
64
|
+
|
64
65
|
Returns:
|
65
66
|
True if archive is valid, False otherwise
|
66
|
-
|
67
|
+
|
67
68
|
Raises:
|
68
69
|
ArchiveError: If validation cannot be performed
|
69
70
|
"""
|
70
|
-
pass
|
71
|
+
pass
|
@@ -1,8 +1,8 @@
|
|
1
1
|
"""BZIP2 compression implementation."""
|
2
2
|
|
3
3
|
import bz2
|
4
|
-
import shutil
|
5
4
|
from pathlib import Path
|
5
|
+
import shutil
|
6
6
|
from typing import BinaryIO
|
7
7
|
|
8
8
|
from attrs import define, field
|
@@ -18,118 +18,118 @@ logger = get_logger(__name__)
|
|
18
18
|
class Bzip2Compressor:
|
19
19
|
"""
|
20
20
|
BZIP2 compression implementation.
|
21
|
-
|
21
|
+
|
22
22
|
Provides BZIP2 compression and decompression for single files.
|
23
23
|
Does not handle bundling - use with TarArchive for .tar.bz2 files.
|
24
24
|
"""
|
25
|
-
|
25
|
+
|
26
26
|
level: int = field(default=9) # Compression level 1-9 (1=fast, 9=best)
|
27
|
-
|
27
|
+
|
28
28
|
@level.validator
|
29
|
-
def _validate_level(self, attribute, value):
|
29
|
+
def _validate_level(self, attribute: object, value: int) -> None:
|
30
30
|
if not 1 <= value <= 9:
|
31
31
|
raise ValueError(f"Compression level must be 1-9, got {value}")
|
32
|
-
|
32
|
+
|
33
33
|
def compress(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
|
34
34
|
"""
|
35
35
|
Compress data from input stream to output stream.
|
36
|
-
|
36
|
+
|
37
37
|
Args:
|
38
38
|
input_stream: Input binary stream
|
39
39
|
output_stream: Output binary stream
|
40
|
-
|
40
|
+
|
41
41
|
Raises:
|
42
42
|
ArchiveError: If compression fails
|
43
43
|
"""
|
44
44
|
try:
|
45
|
-
with bz2.BZ2File(output_stream,
|
45
|
+
with bz2.BZ2File(output_stream, "wb", compresslevel=self.level) as bz:
|
46
46
|
shutil.copyfileobj(input_stream, bz)
|
47
47
|
logger.debug(f"Compressed data with BZIP2 level {self.level}")
|
48
48
|
except Exception as e:
|
49
49
|
raise ArchiveError(f"Failed to compress with BZIP2: {e}") from e
|
50
|
-
|
50
|
+
|
51
51
|
def decompress(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
|
52
52
|
"""
|
53
53
|
Decompress data from input stream to output stream.
|
54
|
-
|
54
|
+
|
55
55
|
Args:
|
56
56
|
input_stream: Input binary stream (bzip2 compressed)
|
57
57
|
output_stream: Output binary stream
|
58
|
-
|
58
|
+
|
59
59
|
Raises:
|
60
60
|
ArchiveError: If decompression fails
|
61
61
|
"""
|
62
62
|
try:
|
63
|
-
with bz2.BZ2File(input_stream,
|
63
|
+
with bz2.BZ2File(input_stream, "rb") as bz:
|
64
64
|
shutil.copyfileobj(bz, output_stream)
|
65
65
|
logger.debug("Decompressed BZIP2 data")
|
66
66
|
except Exception as e:
|
67
67
|
raise ArchiveError(f"Failed to decompress BZIP2: {e}") from e
|
68
|
-
|
68
|
+
|
69
69
|
def compress_file(self, input_path: Path, output_path: Path) -> Path:
|
70
70
|
"""
|
71
71
|
Compress a file.
|
72
|
-
|
72
|
+
|
73
73
|
Args:
|
74
74
|
input_path: Input file path
|
75
75
|
output_path: Output file path (should end with .bz2)
|
76
|
-
|
76
|
+
|
77
77
|
Returns:
|
78
78
|
Path to compressed file
|
79
|
-
|
79
|
+
|
80
80
|
Raises:
|
81
81
|
ArchiveError: If compression fails
|
82
82
|
"""
|
83
83
|
try:
|
84
84
|
ensure_parent_dir(output_path)
|
85
|
-
|
86
|
-
with open(input_path,
|
87
|
-
with bz2.open(output_path,
|
85
|
+
|
86
|
+
with open(input_path, "rb") as f_in:
|
87
|
+
with bz2.open(output_path, "wb", compresslevel=self.level) as f_out:
|
88
88
|
shutil.copyfileobj(f_in, f_out)
|
89
|
-
|
89
|
+
|
90
90
|
logger.debug(f"Compressed {input_path} to {output_path}")
|
91
91
|
return output_path
|
92
|
-
|
92
|
+
|
93
93
|
except Exception as e:
|
94
94
|
raise ArchiveError(f"Failed to compress file: {e}") from e
|
95
|
-
|
95
|
+
|
96
96
|
def decompress_file(self, input_path: Path, output_path: Path) -> Path:
|
97
97
|
"""
|
98
98
|
Decompress a file.
|
99
|
-
|
99
|
+
|
100
100
|
Args:
|
101
101
|
input_path: Input file path (bzip2 compressed)
|
102
102
|
output_path: Output file path
|
103
|
-
|
103
|
+
|
104
104
|
Returns:
|
105
105
|
Path to decompressed file
|
106
|
-
|
106
|
+
|
107
107
|
Raises:
|
108
108
|
ArchiveError: If decompression fails
|
109
109
|
"""
|
110
110
|
try:
|
111
111
|
ensure_parent_dir(output_path)
|
112
|
-
|
113
|
-
with bz2.open(input_path,
|
114
|
-
with open(output_path,
|
112
|
+
|
113
|
+
with bz2.open(input_path, "rb") as f_in:
|
114
|
+
with open(output_path, "wb") as f_out:
|
115
115
|
shutil.copyfileobj(f_in, f_out)
|
116
|
-
|
116
|
+
|
117
117
|
logger.debug(f"Decompressed {input_path} to {output_path}")
|
118
118
|
return output_path
|
119
|
-
|
119
|
+
|
120
120
|
except Exception as e:
|
121
121
|
raise ArchiveError(f"Failed to decompress file: {e}") from e
|
122
|
-
|
122
|
+
|
123
123
|
def compress_bytes(self, data: bytes) -> bytes:
|
124
124
|
"""
|
125
125
|
Compress bytes data.
|
126
|
-
|
126
|
+
|
127
127
|
Args:
|
128
128
|
data: Input bytes
|
129
|
-
|
129
|
+
|
130
130
|
Returns:
|
131
131
|
Compressed bytes
|
132
|
-
|
132
|
+
|
133
133
|
Raises:
|
134
134
|
ArchiveError: If compression fails
|
135
135
|
"""
|
@@ -137,21 +137,21 @@ class Bzip2Compressor:
|
|
137
137
|
return bz2.compress(data, compresslevel=self.level)
|
138
138
|
except Exception as e:
|
139
139
|
raise ArchiveError(f"Failed to compress bytes: {e}") from e
|
140
|
-
|
140
|
+
|
141
141
|
def decompress_bytes(self, data: bytes) -> bytes:
|
142
142
|
"""
|
143
143
|
Decompress bytes data.
|
144
|
-
|
144
|
+
|
145
145
|
Args:
|
146
146
|
data: Compressed bytes
|
147
|
-
|
147
|
+
|
148
148
|
Returns:
|
149
149
|
Decompressed bytes
|
150
|
-
|
150
|
+
|
151
151
|
Raises:
|
152
152
|
ArchiveError: If decompression fails
|
153
153
|
"""
|
154
154
|
try:
|
155
155
|
return bz2.decompress(data)
|
156
156
|
except Exception as e:
|
157
|
-
raise ArchiveError(f"Failed to decompress bytes: {e}") from e
|
157
|
+
raise ArchiveError(f"Failed to decompress bytes: {e}") from e
|
@@ -1,16 +1,14 @@
|
|
1
1
|
"""GZIP compression implementation."""
|
2
2
|
|
3
3
|
import gzip
|
4
|
-
import shutil
|
5
|
-
from io import BytesIO
|
6
4
|
from pathlib import Path
|
5
|
+
import shutil
|
7
6
|
from typing import BinaryIO
|
8
7
|
|
9
8
|
from attrs import define, field
|
10
9
|
|
11
10
|
from provide.foundation.archive.base import ArchiveError
|
12
11
|
from provide.foundation.file import ensure_parent_dir
|
13
|
-
from provide.foundation.file.utils import get_size
|
14
12
|
from provide.foundation.logger import get_logger
|
15
13
|
|
16
14
|
logger = get_logger(__name__)
|
@@ -20,118 +18,120 @@ logger = get_logger(__name__)
|
|
20
18
|
class GzipCompressor:
|
21
19
|
"""
|
22
20
|
GZIP compression implementation.
|
23
|
-
|
21
|
+
|
24
22
|
Provides GZIP compression and decompression for single files.
|
25
23
|
Does not handle bundling - use with TarArchive for .tar.gz files.
|
26
24
|
"""
|
27
|
-
|
25
|
+
|
28
26
|
level: int = field(default=6) # Compression level 1-9 (1=fast, 9=best)
|
29
|
-
|
27
|
+
|
30
28
|
@level.validator
|
31
|
-
def _validate_level(self, attribute, value):
|
29
|
+
def _validate_level(self, attribute: object, value: int) -> None:
|
32
30
|
if not 1 <= value <= 9:
|
33
31
|
raise ValueError(f"Compression level must be 1-9, got {value}")
|
34
|
-
|
32
|
+
|
35
33
|
def compress(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
|
36
34
|
"""
|
37
35
|
Compress data from input stream to output stream.
|
38
|
-
|
36
|
+
|
39
37
|
Args:
|
40
38
|
input_stream: Input binary stream
|
41
39
|
output_stream: Output binary stream
|
42
|
-
|
40
|
+
|
43
41
|
Raises:
|
44
42
|
ArchiveError: If compression fails
|
45
43
|
"""
|
46
44
|
try:
|
47
|
-
with gzip.GzipFile(
|
45
|
+
with gzip.GzipFile(
|
46
|
+
fileobj=output_stream, mode="wb", compresslevel=self.level
|
47
|
+
) as gz:
|
48
48
|
shutil.copyfileobj(input_stream, gz)
|
49
49
|
logger.debug(f"Compressed data with GZIP level {self.level}")
|
50
50
|
except Exception as e:
|
51
51
|
raise ArchiveError(f"Failed to compress with GZIP: {e}") from e
|
52
|
-
|
52
|
+
|
53
53
|
def decompress(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
|
54
54
|
"""
|
55
55
|
Decompress data from input stream to output stream.
|
56
|
-
|
56
|
+
|
57
57
|
Args:
|
58
58
|
input_stream: Input binary stream (gzipped)
|
59
59
|
output_stream: Output binary stream
|
60
|
-
|
60
|
+
|
61
61
|
Raises:
|
62
62
|
ArchiveError: If decompression fails
|
63
63
|
"""
|
64
64
|
try:
|
65
|
-
with gzip.GzipFile(fileobj=input_stream, mode=
|
65
|
+
with gzip.GzipFile(fileobj=input_stream, mode="rb") as gz:
|
66
66
|
shutil.copyfileobj(gz, output_stream)
|
67
67
|
logger.debug("Decompressed GZIP data")
|
68
68
|
except Exception as e:
|
69
69
|
raise ArchiveError(f"Failed to decompress GZIP: {e}") from e
|
70
|
-
|
70
|
+
|
71
71
|
def compress_file(self, input_path: Path, output_path: Path) -> Path:
|
72
72
|
"""
|
73
73
|
Compress a file.
|
74
|
-
|
74
|
+
|
75
75
|
Args:
|
76
76
|
input_path: Input file path
|
77
77
|
output_path: Output file path (should end with .gz)
|
78
|
-
|
78
|
+
|
79
79
|
Returns:
|
80
80
|
Path to compressed file
|
81
|
-
|
81
|
+
|
82
82
|
Raises:
|
83
83
|
ArchiveError: If compression fails
|
84
84
|
"""
|
85
85
|
try:
|
86
86
|
ensure_parent_dir(output_path)
|
87
|
-
|
88
|
-
with open(input_path,
|
89
|
-
with gzip.open(output_path,
|
87
|
+
|
88
|
+
with open(input_path, "rb") as f_in:
|
89
|
+
with gzip.open(output_path, "wb", compresslevel=self.level) as f_out:
|
90
90
|
shutil.copyfileobj(f_in, f_out)
|
91
|
-
|
91
|
+
|
92
92
|
logger.debug(f"Compressed {input_path} to {output_path}")
|
93
93
|
return output_path
|
94
|
-
|
94
|
+
|
95
95
|
except Exception as e:
|
96
96
|
raise ArchiveError(f"Failed to compress file: {e}") from e
|
97
|
-
|
97
|
+
|
98
98
|
def decompress_file(self, input_path: Path, output_path: Path) -> Path:
|
99
99
|
"""
|
100
100
|
Decompress a file.
|
101
|
-
|
101
|
+
|
102
102
|
Args:
|
103
103
|
input_path: Input file path (gzipped)
|
104
104
|
output_path: Output file path
|
105
|
-
|
105
|
+
|
106
106
|
Returns:
|
107
107
|
Path to decompressed file
|
108
|
-
|
108
|
+
|
109
109
|
Raises:
|
110
110
|
ArchiveError: If decompression fails
|
111
111
|
"""
|
112
112
|
try:
|
113
113
|
ensure_parent_dir(output_path)
|
114
|
-
|
115
|
-
with gzip.open(input_path,
|
116
|
-
with open(output_path,
|
114
|
+
|
115
|
+
with gzip.open(input_path, "rb") as f_in:
|
116
|
+
with open(output_path, "wb") as f_out:
|
117
117
|
shutil.copyfileobj(f_in, f_out)
|
118
|
-
|
118
|
+
|
119
119
|
logger.debug(f"Decompressed {input_path} to {output_path}")
|
120
120
|
return output_path
|
121
|
-
|
121
|
+
|
122
122
|
except Exception as e:
|
123
123
|
raise ArchiveError(f"Failed to decompress file: {e}") from e
|
124
|
-
|
124
|
+
|
125
125
|
def compress_bytes(self, data: bytes) -> bytes:
|
126
126
|
"""
|
127
127
|
Compress bytes data.
|
128
|
-
|
128
|
+
|
129
129
|
Args:
|
130
130
|
data: Input bytes
|
131
|
-
|
131
|
+
|
132
132
|
Returns:
|
133
133
|
Compressed bytes
|
134
|
-
|
134
|
+
|
135
135
|
Raises:
|
136
136
|
ArchiveError: If compression fails
|
137
137
|
"""
|
@@ -139,21 +139,21 @@ class GzipCompressor:
|
|
139
139
|
return gzip.compress(data, compresslevel=self.level)
|
140
140
|
except Exception as e:
|
141
141
|
raise ArchiveError(f"Failed to compress bytes: {e}") from e
|
142
|
-
|
142
|
+
|
143
143
|
def decompress_bytes(self, data: bytes) -> bytes:
|
144
144
|
"""
|
145
145
|
Decompress bytes data.
|
146
|
-
|
146
|
+
|
147
147
|
Args:
|
148
148
|
data: Compressed bytes
|
149
|
-
|
149
|
+
|
150
150
|
Returns:
|
151
151
|
Decompressed bytes
|
152
|
-
|
152
|
+
|
153
153
|
Raises:
|
154
154
|
ArchiveError: If decompression fails
|
155
155
|
"""
|
156
156
|
try:
|
157
157
|
return gzip.decompress(data)
|
158
158
|
except Exception as e:
|
159
|
-
raise ArchiveError(f"Failed to decompress bytes: {e}") from e
|
159
|
+
raise ArchiveError(f"Failed to decompress bytes: {e}") from e
|