provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev2__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 +41 -23
- provide/foundation/archive/__init__.py +23 -0
- provide/foundation/archive/base.py +70 -0
- provide/foundation/archive/bzip2.py +157 -0
- provide/foundation/archive/gzip.py +159 -0
- provide/foundation/archive/operations.py +334 -0
- provide/foundation/archive/tar.py +164 -0
- provide/foundation/archive/zip.py +203 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +13 -7
- provide/foundation/cli/commands/logs/__init__.py +1 -1
- provide/foundation/cli/commands/logs/query.py +1 -1
- provide/foundation/cli/commands/logs/send.py +1 -1
- provide/foundation/cli/commands/logs/tail.py +1 -1
- provide/foundation/cli/decorators.py +11 -10
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -35
- provide/foundation/cli/utils.py +21 -17
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +479 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +4 -19
- provide/foundation/config/loader.py +9 -3
- provide/foundation/config/sync.py +19 -4
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +35 -13
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +85 -109
- provide/foundation/core.py +1 -2
- provide/foundation/crypto/__init__.py +2 -0
- provide/foundation/crypto/certificates/__init__.py +34 -0
- provide/foundation/crypto/certificates/base.py +173 -0
- provide/foundation/crypto/certificates/certificate.py +290 -0
- provide/foundation/crypto/certificates/factory.py +213 -0
- provide/foundation/crypto/certificates/generator.py +138 -0
- provide/foundation/crypto/certificates/loader.py +130 -0
- provide/foundation/crypto/certificates/operations.py +198 -0
- provide/foundation/crypto/certificates/trust.py +107 -0
- provide/foundation/errors/__init__.py +2 -3
- provide/foundation/errors/decorators.py +0 -231
- provide/foundation/errors/types.py +0 -97
- provide/foundation/eventsets/__init__.py +0 -0
- provide/foundation/eventsets/display.py +84 -0
- provide/foundation/eventsets/registry.py +160 -0
- provide/foundation/eventsets/resolver.py +192 -0
- provide/foundation/eventsets/sets/das.py +128 -0
- provide/foundation/eventsets/sets/database.py +125 -0
- provide/foundation/eventsets/sets/http.py +153 -0
- provide/foundation/eventsets/sets/llm.py +139 -0
- provide/foundation/eventsets/sets/task_queue.py +107 -0
- provide/foundation/eventsets/types.py +70 -0
- provide/foundation/file/directory.py +13 -22
- provide/foundation/file/lock.py +3 -1
- provide/foundation/hub/components.py +77 -515
- provide/foundation/hub/config.py +151 -0
- provide/foundation/hub/discovery.py +62 -0
- provide/foundation/hub/handlers.py +81 -0
- provide/foundation/hub/lifecycle.py +194 -0
- provide/foundation/hub/manager.py +4 -4
- provide/foundation/hub/processors.py +44 -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 +12 -12
- provide/foundation/{observability → integrations}/openobserve/commands.py +3 -3
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/streaming.py +4 -4
- provide/foundation/logger/__init__.py +3 -10
- provide/foundation/logger/config/logging.py +68 -298
- provide/foundation/logger/config/telemetry.py +41 -121
- provide/foundation/logger/core.py +0 -2
- provide/foundation/logger/custom_processors.py +1 -0
- provide/foundation/logger/factories.py +11 -2
- provide/foundation/logger/processors/main.py +20 -84
- provide/foundation/logger/setup/__init__.py +5 -1
- provide/foundation/logger/setup/coordinator.py +76 -24
- provide/foundation/logger/setup/processors.py +2 -9
- provide/foundation/logger/trace.py +27 -0
- provide/foundation/metrics/otel.py +10 -10
- provide/foundation/observability/__init__.py +2 -2
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +47 -0
- provide/foundation/process/lifecycle.py +115 -59
- provide/foundation/resilience/__init__.py +35 -0
- provide/foundation/resilience/circuit.py +164 -0
- provide/foundation/resilience/decorators.py +220 -0
- provide/foundation/resilience/fallback.py +193 -0
- provide/foundation/resilience/retry.py +325 -0
- provide/foundation/streams/config.py +79 -0
- provide/foundation/streams/console.py +7 -8
- provide/foundation/streams/core.py +6 -3
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +84 -2
- provide/foundation/testing/archive/__init__.py +24 -0
- provide/foundation/testing/archive/fixtures.py +217 -0
- provide/foundation/testing/cli.py +30 -17
- provide/foundation/testing/common/__init__.py +32 -0
- provide/foundation/testing/common/fixtures.py +236 -0
- provide/foundation/testing/file/__init__.py +40 -0
- provide/foundation/testing/file/content_fixtures.py +316 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +52 -0
- provide/foundation/testing/file/special_fixtures.py +153 -0
- provide/foundation/testing/logger.py +117 -11
- provide/foundation/testing/mocking/__init__.py +46 -0
- provide/foundation/testing/mocking/fixtures.py +331 -0
- provide/foundation/testing/process/__init__.py +48 -0
- provide/foundation/testing/process/async_fixtures.py +405 -0
- provide/foundation/testing/process/fixtures.py +56 -0
- provide/foundation/testing/process/subprocess_fixtures.py +209 -0
- provide/foundation/testing/threading/__init__.py +38 -0
- provide/foundation/testing/threading/basic_fixtures.py +101 -0
- provide/foundation/testing/threading/data_fixtures.py +99 -0
- provide/foundation/testing/threading/execution_fixtures.py +263 -0
- provide/foundation/testing/threading/fixtures.py +54 -0
- provide/foundation/testing/threading/sync_fixtures.py +97 -0
- provide/foundation/testing/time/__init__.py +32 -0
- provide/foundation/testing/time/fixtures.py +409 -0
- provide/foundation/testing/transport/__init__.py +30 -0
- provide/foundation/testing/transport/fixtures.py +280 -0
- provide/foundation/tools/__init__.py +58 -0
- provide/foundation/tools/base.py +348 -0
- provide/foundation/tools/cache.py +268 -0
- provide/foundation/tools/downloader.py +224 -0
- provide/foundation/tools/installer.py +254 -0
- provide/foundation/tools/registry.py +223 -0
- provide/foundation/tools/resolver.py +321 -0
- provide/foundation/tools/verifier.py +186 -0
- provide/foundation/tracer/otel.py +7 -11
- provide/foundation/tracer/spans.py +2 -2
- provide/foundation/transport/__init__.py +155 -0
- provide/foundation/transport/base.py +171 -0
- provide/foundation/transport/client.py +266 -0
- provide/foundation/transport/config.py +140 -0
- provide/foundation/transport/errors.py +79 -0
- provide/foundation/transport/http.py +232 -0
- provide/foundation/transport/middleware.py +360 -0
- provide/foundation/transport/registry.py +167 -0
- provide/foundation/transport/types.py +45 -0
- provide/foundation/utils/deps.py +14 -12
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/METADATA +5 -28
- provide_foundation-0.0.0.dev2.dist-info/RECORD +225 -0
- provide/foundation/cli/commands/logs/generate_old.py +0 -569
- provide/foundation/crypto/certificates.py +0 -896
- provide/foundation/logger/emoji/__init__.py +0 -44
- provide/foundation/logger/emoji/matrix.py +0 -209
- provide/foundation/logger/emoji/sets.py +0 -458
- provide/foundation/logger/emoji/types.py +0 -56
- provide/foundation/logger/setup/emoji_resolver.py +0 -64
- provide_foundation-0.0.0.dev0.dist-info/RECORD +0 -149
- /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
- /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.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,19 @@ from provide.foundation.errors import (
|
|
27
27
|
with_error_handling,
|
28
28
|
)
|
29
29
|
|
30
|
+
# Resilience exports
|
31
|
+
from provide.foundation.resilience import (
|
32
|
+
retry,
|
33
|
+
circuit_breaker,
|
34
|
+
fallback,
|
35
|
+
RetryPolicy,
|
36
|
+
RetryExecutor,
|
37
|
+
BackoffStrategy,
|
38
|
+
CircuitBreaker,
|
39
|
+
CircuitState,
|
40
|
+
FallbackChain,
|
41
|
+
)
|
42
|
+
|
30
43
|
# Hub and Registry exports (public API)
|
31
44
|
from provide.foundation.hub.components import ComponentCategory, get_component_registry
|
32
45
|
from provide.foundation.hub.manager import Hub, clear_hub, get_hub
|
@@ -39,17 +52,12 @@ from provide.foundation.logger import (
|
|
39
52
|
setup_logging, # Setup function (backward compatibility)
|
40
53
|
)
|
41
54
|
|
42
|
-
#
|
43
|
-
from provide.foundation.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
)
|
49
|
-
from provide.foundation.logger.emoji.types import (
|
50
|
-
EmojiSet,
|
51
|
-
EmojiSetConfig,
|
52
|
-
FieldToEmojiMapping,
|
55
|
+
# Event set exports
|
56
|
+
from provide.foundation.eventsets.display import show_event_matrix
|
57
|
+
from provide.foundation.eventsets.types import (
|
58
|
+
EventMapping,
|
59
|
+
EventSet,
|
60
|
+
FieldMapping,
|
53
61
|
)
|
54
62
|
from provide.foundation.setup import (
|
55
63
|
setup_telemetry,
|
@@ -93,15 +101,16 @@ def __getattr__(name: str):
|
|
93
101
|
|
94
102
|
|
95
103
|
__all__ = [
|
96
|
-
#
|
97
|
-
"
|
98
|
-
"SECONDARY_EMOJI",
|
99
|
-
"TERTIARY_EMOJI",
|
104
|
+
# Event enrichment utilities
|
105
|
+
"show_event_matrix",
|
100
106
|
"ConsoleFormatterStr",
|
101
107
|
# New foundation modules
|
102
|
-
"
|
103
|
-
#
|
104
|
-
|
108
|
+
"CLIContext",
|
109
|
+
"Context", # Backward compatibility
|
110
|
+
# Event set types
|
111
|
+
"EventMapping",
|
112
|
+
"EventSet",
|
113
|
+
"FieldMapping",
|
105
114
|
# Error handling essentials
|
106
115
|
"FoundationError",
|
107
116
|
# Type aliases
|
@@ -115,8 +124,6 @@ __all__ = [
|
|
115
124
|
"get_component_registry",
|
116
125
|
"get_hub",
|
117
126
|
"clear_hub",
|
118
|
-
"FieldToEmojiMapping",
|
119
|
-
"EmojiSetConfig",
|
120
127
|
# Configuration classes
|
121
128
|
"TelemetryConfig",
|
122
129
|
# Version
|
@@ -136,12 +143,23 @@ __all__ = [
|
|
136
143
|
"pout",
|
137
144
|
"platform",
|
138
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)
|
139
157
|
"retry_on_error",
|
158
|
+
"resilience", # The resilience module for detailed imports
|
140
159
|
"setup_logging", # Backward compatibility
|
141
160
|
"setup_logger", # Consistent naming
|
142
161
|
"setup_telemetry",
|
143
162
|
# Utilities
|
144
|
-
"show_emoji_matrix",
|
145
163
|
"shutdown_foundation_telemetry",
|
146
164
|
"timed_block",
|
147
165
|
# Rate limiting utilities
|
@@ -0,0 +1,23 @@
|
|
1
|
+
"""Archive operations for provide-foundation.
|
2
|
+
|
3
|
+
This module provides clean, composable archive operations without complex abstractions.
|
4
|
+
Tools for creating, extracting, and manipulating archives in various formats.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from provide.foundation.archive.base import ArchiveError, BaseArchive
|
8
|
+
from provide.foundation.archive.bzip2 import Bzip2Compressor
|
9
|
+
from provide.foundation.archive.gzip import GzipCompressor
|
10
|
+
from provide.foundation.archive.operations import ArchiveOperations, OperationChain
|
11
|
+
from provide.foundation.archive.tar import TarArchive
|
12
|
+
from provide.foundation.archive.zip import ZipArchive
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
"ArchiveError",
|
16
|
+
"ArchiveOperations",
|
17
|
+
"BaseArchive",
|
18
|
+
"Bzip2Compressor",
|
19
|
+
"GzipCompressor",
|
20
|
+
"OperationChain",
|
21
|
+
"TarArchive",
|
22
|
+
"ZipArchive",
|
23
|
+
]
|
@@ -0,0 +1,70 @@
|
|
1
|
+
"""Base classes and interfaces for archive operations."""
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from provide.foundation.errors import FoundationError
|
7
|
+
|
8
|
+
|
9
|
+
class ArchiveError(FoundationError):
|
10
|
+
"""Base exception for archive-related errors."""
|
11
|
+
pass
|
12
|
+
|
13
|
+
|
14
|
+
class BaseArchive(ABC):
|
15
|
+
"""
|
16
|
+
Abstract base class for all archive implementations.
|
17
|
+
|
18
|
+
This defines the common interface that all archive implementations
|
19
|
+
must follow, ensuring consistency across different archive formats.
|
20
|
+
"""
|
21
|
+
|
22
|
+
@abstractmethod
|
23
|
+
def create(self, source: Path, output: Path) -> Path:
|
24
|
+
"""
|
25
|
+
Create an archive from source path.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
source: Source file or directory to archive
|
29
|
+
output: Output archive file path
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
Path to the created archive file
|
33
|
+
|
34
|
+
Raises:
|
35
|
+
ArchiveError: If archive creation fails
|
36
|
+
"""
|
37
|
+
pass
|
38
|
+
|
39
|
+
@abstractmethod
|
40
|
+
def extract(self, archive: Path, output: Path) -> Path:
|
41
|
+
"""
|
42
|
+
Extract an archive to output path.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
archive: Archive file to extract
|
46
|
+
output: Output directory for extracted contents
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
Path to the extraction directory
|
50
|
+
|
51
|
+
Raises:
|
52
|
+
ArchiveError: If extraction fails
|
53
|
+
"""
|
54
|
+
pass
|
55
|
+
|
56
|
+
@abstractmethod
|
57
|
+
def validate(self, archive: Path) -> bool:
|
58
|
+
"""
|
59
|
+
Validate that an archive is properly formed.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
archive: Archive file to validate
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
True if archive is valid, False otherwise
|
66
|
+
|
67
|
+
Raises:
|
68
|
+
ArchiveError: If validation cannot be performed
|
69
|
+
"""
|
70
|
+
pass
|
@@ -0,0 +1,157 @@
|
|
1
|
+
"""BZIP2 compression implementation."""
|
2
|
+
|
3
|
+
import bz2
|
4
|
+
import shutil
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import BinaryIO
|
7
|
+
|
8
|
+
from attrs import define, field
|
9
|
+
|
10
|
+
from provide.foundation.archive.base import ArchiveError
|
11
|
+
from provide.foundation.file import ensure_parent_dir
|
12
|
+
from provide.foundation.logger import get_logger
|
13
|
+
|
14
|
+
logger = get_logger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
@define(slots=True)
|
18
|
+
class Bzip2Compressor:
|
19
|
+
"""
|
20
|
+
BZIP2 compression implementation.
|
21
|
+
|
22
|
+
Provides BZIP2 compression and decompression for single files.
|
23
|
+
Does not handle bundling - use with TarArchive for .tar.bz2 files.
|
24
|
+
"""
|
25
|
+
|
26
|
+
level: int = field(default=9) # Compression level 1-9 (1=fast, 9=best)
|
27
|
+
|
28
|
+
@level.validator
|
29
|
+
def _validate_level(self, attribute, value):
|
30
|
+
if not 1 <= value <= 9:
|
31
|
+
raise ValueError(f"Compression level must be 1-9, got {value}")
|
32
|
+
|
33
|
+
def compress(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
|
34
|
+
"""
|
35
|
+
Compress data from input stream to output stream.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
input_stream: Input binary stream
|
39
|
+
output_stream: Output binary stream
|
40
|
+
|
41
|
+
Raises:
|
42
|
+
ArchiveError: If compression fails
|
43
|
+
"""
|
44
|
+
try:
|
45
|
+
with bz2.BZ2File(output_stream, 'wb', compresslevel=self.level) as bz:
|
46
|
+
shutil.copyfileobj(input_stream, bz)
|
47
|
+
logger.debug(f"Compressed data with BZIP2 level {self.level}")
|
48
|
+
except Exception as e:
|
49
|
+
raise ArchiveError(f"Failed to compress with BZIP2: {e}") from e
|
50
|
+
|
51
|
+
def decompress(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
|
52
|
+
"""
|
53
|
+
Decompress data from input stream to output stream.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
input_stream: Input binary stream (bzip2 compressed)
|
57
|
+
output_stream: Output binary stream
|
58
|
+
|
59
|
+
Raises:
|
60
|
+
ArchiveError: If decompression fails
|
61
|
+
"""
|
62
|
+
try:
|
63
|
+
with bz2.BZ2File(input_stream, 'rb') as bz:
|
64
|
+
shutil.copyfileobj(bz, output_stream)
|
65
|
+
logger.debug("Decompressed BZIP2 data")
|
66
|
+
except Exception as e:
|
67
|
+
raise ArchiveError(f"Failed to decompress BZIP2: {e}") from e
|
68
|
+
|
69
|
+
def compress_file(self, input_path: Path, output_path: Path) -> Path:
|
70
|
+
"""
|
71
|
+
Compress a file.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
input_path: Input file path
|
75
|
+
output_path: Output file path (should end with .bz2)
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
Path to compressed file
|
79
|
+
|
80
|
+
Raises:
|
81
|
+
ArchiveError: If compression fails
|
82
|
+
"""
|
83
|
+
try:
|
84
|
+
ensure_parent_dir(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
|
+
shutil.copyfileobj(f_in, f_out)
|
89
|
+
|
90
|
+
logger.debug(f"Compressed {input_path} to {output_path}")
|
91
|
+
return output_path
|
92
|
+
|
93
|
+
except Exception as e:
|
94
|
+
raise ArchiveError(f"Failed to compress file: {e}") from e
|
95
|
+
|
96
|
+
def decompress_file(self, input_path: Path, output_path: Path) -> Path:
|
97
|
+
"""
|
98
|
+
Decompress a file.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
input_path: Input file path (bzip2 compressed)
|
102
|
+
output_path: Output file path
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
Path to decompressed file
|
106
|
+
|
107
|
+
Raises:
|
108
|
+
ArchiveError: If decompression fails
|
109
|
+
"""
|
110
|
+
try:
|
111
|
+
ensure_parent_dir(output_path)
|
112
|
+
|
113
|
+
with bz2.open(input_path, 'rb') as f_in:
|
114
|
+
with open(output_path, 'wb') as f_out:
|
115
|
+
shutil.copyfileobj(f_in, f_out)
|
116
|
+
|
117
|
+
logger.debug(f"Decompressed {input_path} to {output_path}")
|
118
|
+
return output_path
|
119
|
+
|
120
|
+
except Exception as e:
|
121
|
+
raise ArchiveError(f"Failed to decompress file: {e}") from e
|
122
|
+
|
123
|
+
def compress_bytes(self, data: bytes) -> bytes:
|
124
|
+
"""
|
125
|
+
Compress bytes data.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
data: Input bytes
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
Compressed bytes
|
132
|
+
|
133
|
+
Raises:
|
134
|
+
ArchiveError: If compression fails
|
135
|
+
"""
|
136
|
+
try:
|
137
|
+
return bz2.compress(data, compresslevel=self.level)
|
138
|
+
except Exception as e:
|
139
|
+
raise ArchiveError(f"Failed to compress bytes: {e}") from e
|
140
|
+
|
141
|
+
def decompress_bytes(self, data: bytes) -> bytes:
|
142
|
+
"""
|
143
|
+
Decompress bytes data.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
data: Compressed bytes
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
Decompressed bytes
|
150
|
+
|
151
|
+
Raises:
|
152
|
+
ArchiveError: If decompression fails
|
153
|
+
"""
|
154
|
+
try:
|
155
|
+
return bz2.decompress(data)
|
156
|
+
except Exception as e:
|
157
|
+
raise ArchiveError(f"Failed to decompress bytes: {e}") from e
|
@@ -0,0 +1,159 @@
|
|
1
|
+
"""GZIP compression implementation."""
|
2
|
+
|
3
|
+
import gzip
|
4
|
+
import shutil
|
5
|
+
from io import BytesIO
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import BinaryIO
|
8
|
+
|
9
|
+
from attrs import define, field
|
10
|
+
|
11
|
+
from provide.foundation.archive.base import ArchiveError
|
12
|
+
from provide.foundation.file import ensure_parent_dir
|
13
|
+
from provide.foundation.file.utils import get_size
|
14
|
+
from provide.foundation.logger import get_logger
|
15
|
+
|
16
|
+
logger = get_logger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
@define(slots=True)
|
20
|
+
class GzipCompressor:
|
21
|
+
"""
|
22
|
+
GZIP compression implementation.
|
23
|
+
|
24
|
+
Provides GZIP compression and decompression for single files.
|
25
|
+
Does not handle bundling - use with TarArchive for .tar.gz files.
|
26
|
+
"""
|
27
|
+
|
28
|
+
level: int = field(default=6) # Compression level 1-9 (1=fast, 9=best)
|
29
|
+
|
30
|
+
@level.validator
|
31
|
+
def _validate_level(self, attribute, value):
|
32
|
+
if not 1 <= value <= 9:
|
33
|
+
raise ValueError(f"Compression level must be 1-9, got {value}")
|
34
|
+
|
35
|
+
def compress(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
|
36
|
+
"""
|
37
|
+
Compress data from input stream to output stream.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
input_stream: Input binary stream
|
41
|
+
output_stream: Output binary stream
|
42
|
+
|
43
|
+
Raises:
|
44
|
+
ArchiveError: If compression fails
|
45
|
+
"""
|
46
|
+
try:
|
47
|
+
with gzip.GzipFile(fileobj=output_stream, mode='wb', compresslevel=self.level) as gz:
|
48
|
+
shutil.copyfileobj(input_stream, gz)
|
49
|
+
logger.debug(f"Compressed data with GZIP level {self.level}")
|
50
|
+
except Exception as e:
|
51
|
+
raise ArchiveError(f"Failed to compress with GZIP: {e}") from e
|
52
|
+
|
53
|
+
def decompress(self, input_stream: BinaryIO, output_stream: BinaryIO) -> None:
|
54
|
+
"""
|
55
|
+
Decompress data from input stream to output stream.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
input_stream: Input binary stream (gzipped)
|
59
|
+
output_stream: Output binary stream
|
60
|
+
|
61
|
+
Raises:
|
62
|
+
ArchiveError: If decompression fails
|
63
|
+
"""
|
64
|
+
try:
|
65
|
+
with gzip.GzipFile(fileobj=input_stream, mode='rb') as gz:
|
66
|
+
shutil.copyfileobj(gz, output_stream)
|
67
|
+
logger.debug("Decompressed GZIP data")
|
68
|
+
except Exception as e:
|
69
|
+
raise ArchiveError(f"Failed to decompress GZIP: {e}") from e
|
70
|
+
|
71
|
+
def compress_file(self, input_path: Path, output_path: Path) -> Path:
|
72
|
+
"""
|
73
|
+
Compress a file.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
input_path: Input file path
|
77
|
+
output_path: Output file path (should end with .gz)
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
Path to compressed file
|
81
|
+
|
82
|
+
Raises:
|
83
|
+
ArchiveError: If compression fails
|
84
|
+
"""
|
85
|
+
try:
|
86
|
+
ensure_parent_dir(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
|
+
shutil.copyfileobj(f_in, f_out)
|
91
|
+
|
92
|
+
logger.debug(f"Compressed {input_path} to {output_path}")
|
93
|
+
return output_path
|
94
|
+
|
95
|
+
except Exception as e:
|
96
|
+
raise ArchiveError(f"Failed to compress file: {e}") from e
|
97
|
+
|
98
|
+
def decompress_file(self, input_path: Path, output_path: Path) -> Path:
|
99
|
+
"""
|
100
|
+
Decompress a file.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
input_path: Input file path (gzipped)
|
104
|
+
output_path: Output file path
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
Path to decompressed file
|
108
|
+
|
109
|
+
Raises:
|
110
|
+
ArchiveError: If decompression fails
|
111
|
+
"""
|
112
|
+
try:
|
113
|
+
ensure_parent_dir(output_path)
|
114
|
+
|
115
|
+
with gzip.open(input_path, 'rb') as f_in:
|
116
|
+
with open(output_path, 'wb') as f_out:
|
117
|
+
shutil.copyfileobj(f_in, f_out)
|
118
|
+
|
119
|
+
logger.debug(f"Decompressed {input_path} to {output_path}")
|
120
|
+
return output_path
|
121
|
+
|
122
|
+
except Exception as e:
|
123
|
+
raise ArchiveError(f"Failed to decompress file: {e}") from e
|
124
|
+
|
125
|
+
def compress_bytes(self, data: bytes) -> bytes:
|
126
|
+
"""
|
127
|
+
Compress bytes data.
|
128
|
+
|
129
|
+
Args:
|
130
|
+
data: Input bytes
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
Compressed bytes
|
134
|
+
|
135
|
+
Raises:
|
136
|
+
ArchiveError: If compression fails
|
137
|
+
"""
|
138
|
+
try:
|
139
|
+
return gzip.compress(data, compresslevel=self.level)
|
140
|
+
except Exception as e:
|
141
|
+
raise ArchiveError(f"Failed to compress bytes: {e}") from e
|
142
|
+
|
143
|
+
def decompress_bytes(self, data: bytes) -> bytes:
|
144
|
+
"""
|
145
|
+
Decompress bytes data.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
data: Compressed bytes
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
Decompressed bytes
|
152
|
+
|
153
|
+
Raises:
|
154
|
+
ArchiveError: If decompression fails
|
155
|
+
"""
|
156
|
+
try:
|
157
|
+
return gzip.decompress(data)
|
158
|
+
except Exception as e:
|
159
|
+
raise ArchiveError(f"Failed to decompress bytes: {e}") from e
|