pipelet 0.0.2__tar.gz → 0.0.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.
- {pipelet-0.0.2 → pipelet-0.0.4}/PKG-INFO +35 -11
- {pipelet-0.0.2 → pipelet-0.0.4}/README.md +34 -10
- pipelet-0.0.4/pipelet/decarators/__init__.py +3 -0
- pipelet-0.0.4/pipelet/decarators/logging_decorator.py +65 -0
- pipelet-0.0.4/pipelet/decarators/option_decorator.py +36 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/extract/http/http_extractor.py +0 -4
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/csv/reader.py +0 -2
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/json/reader.py +0 -2
- pipelet-0.0.4/pipelet/etl/transform/unzip/__init__.py +7 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/unzip/base.py +2 -5
- pipelet-0.0.4/pipelet/etl/transform/unzip/tar_processor.py +135 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/operations/operations.py +89 -7
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/meta.py +2 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pyproject.toml +1 -1
- pipelet-0.0.2/pipelet/etl/transform/unzip/__init__.py +0 -3
- {pipelet-0.0.2 → pipelet-0.0.4}/LICENSE +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/extract/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/extract/http/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/csv/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/json/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/base.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/csv_processor.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/download_processor.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/file_system.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/unzip_processor.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/factories/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/factories/exceptions_factory.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/factories/file_system_factory.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/log/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/log/logger.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/operations/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/operations/pipeline.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/abc.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/base.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/chain_processors/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/chain_processors/base.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/file_system/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/file_system/base.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/file_system/file_system_types.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/mixins.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/processor_types.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/retry_processor.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/splitter_processor.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/utils/__init__.py +0 -0
- {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/utils/common.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pipelet
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Daniel Naumowich
|
|
6
6
|
Author-email: cskteam4@gmail.com
|
|
@@ -69,10 +69,14 @@ A processor for parsing CSV files using the `pandas` library. It supports numero
|
|
|
69
69
|
### 7. **JsonParser**
|
|
70
70
|
A processor for parsing JSON data from strings or files into Python dictionaries. It supports configuring how numbers, constants (e.g., NaN), and JSON objects are handled with custom functions.
|
|
71
71
|
|
|
72
|
-
### 8. **
|
|
72
|
+
### 8. **UnzipProcessor**
|
|
73
73
|
A processor for extracting files from `.zip` archives. It supports extracting data into files, managing chunk sizes, and deleting the original archive after processing.
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
|
|
76
|
+
### 9. **TarExtractProcessor**
|
|
77
|
+
A processor for extracting files from `".tar", ".gz", ".bz2", ".xz", ".tgz", ".tbz2"` archives. It supports extracting data into files, managing chunk sizes, and deleting the original archive after processing.
|
|
78
|
+
|
|
79
|
+
### 10. **RetryProcessor**
|
|
76
80
|
|
|
77
81
|
A processor designed to handle transient errors by retrying operations based on a customizable retry policy.
|
|
78
82
|
|
|
@@ -91,7 +95,7 @@ A processor designed to handle transient errors by retrying operations based on
|
|
|
91
95
|
from pipelet.processors.http import HttpDataExtractProcessor
|
|
92
96
|
from pipelet.processors.csv_parser import CsvParser
|
|
93
97
|
from pipelet.processors.json_parser import JsonParser
|
|
94
|
-
from pipelet.processors.unzip import
|
|
98
|
+
from pipelet.processors.unzip import UnzipProcessor
|
|
95
99
|
from pipelet.processors.file_system import AbstractFileSystemManager
|
|
96
100
|
|
|
97
101
|
# Create file system manager
|
|
@@ -101,7 +105,7 @@ file_system_manager = AbstractFileSystemManager()
|
|
|
101
105
|
http_processor = HttpDataExtractProcessor(file_system_manager)
|
|
102
106
|
csv_parser = CsvParser(file_system_manager)
|
|
103
107
|
json_parser = JsonParser(file_system_manager)
|
|
104
|
-
unzip_processor =
|
|
108
|
+
unzip_processor = UnzipProcessor(file_system_manager)
|
|
105
109
|
|
|
106
110
|
# Create processor chain
|
|
107
111
|
http_processor.set_next(csv_parser).set_next(json_parser).set_next(unzip_processor)
|
|
@@ -118,7 +122,7 @@ for output in http_processor.process(input_data):
|
|
|
118
122
|
from pipelet.processors.http import HttpDataExtractProcessor
|
|
119
123
|
from pipelet.processors.csv_parser import CsvParser
|
|
120
124
|
from pipelet.processors.json_parser import JsonParser
|
|
121
|
-
from pipelet.processors.unzip import
|
|
125
|
+
from pipelet.processors.unzip import UnzipProcessor
|
|
122
126
|
from pipelet.processors.file_system import AbstractFileSystemManager
|
|
123
127
|
|
|
124
128
|
# Create file system manager
|
|
@@ -128,7 +132,7 @@ file_system_manager = AbstractFileSystemManager()
|
|
|
128
132
|
http_processor = HttpDataExtractProcessor(file_system_manager)
|
|
129
133
|
csv_parser = CsvParser(file_system_manager)
|
|
130
134
|
json_parser = JsonParser(file_system_manager)
|
|
131
|
-
unzip_processor =
|
|
135
|
+
unzip_processor = UnzipProcessor(file_system_manager)
|
|
132
136
|
|
|
133
137
|
# Create processor chain using `>>`
|
|
134
138
|
pipeline = http_processor >> csv_parser >> json_parser >> unzip_processor
|
|
@@ -147,23 +151,35 @@ from pipelet.processors.file_system import FileModeEnum
|
|
|
147
151
|
|
|
148
152
|
def main():
|
|
149
153
|
# Define the pipeline with parallel processing for parsing and unzipping
|
|
154
|
+
|
|
150
155
|
pipeline = [
|
|
151
156
|
Op(
|
|
152
157
|
BaseOpEnum.downloading,
|
|
153
158
|
kwargs={
|
|
154
|
-
"file_system_manager": {
|
|
159
|
+
"file_system_manager": {
|
|
160
|
+
"path": ..., # Here can be your custom file system manager (as an example S3)
|
|
161
|
+
"kwargs": {...},
|
|
162
|
+
},
|
|
155
163
|
"retry_args": {
|
|
164
|
+
"retry_processor": ..., # Here can be your custom retry processor
|
|
156
165
|
"max_retries": 3,
|
|
157
166
|
"delay": 1,
|
|
158
167
|
"delay_step": 1,
|
|
159
168
|
"retry_with_white_exc": True,
|
|
160
169
|
},
|
|
170
|
+
"logging_args": {
|
|
171
|
+
"logger": ..., # Here can be your custom logger
|
|
172
|
+
"logging": True, # Logging intup data
|
|
173
|
+
},
|
|
161
174
|
}
|
|
162
175
|
),
|
|
163
176
|
Op(
|
|
164
177
|
BaseOpEnum.upzip,
|
|
165
178
|
kwargs={
|
|
166
|
-
"file_system_manager": {
|
|
179
|
+
"file_system_manager": {
|
|
180
|
+
"path": ..., # Here can be your custom file system manager (as an example S3)
|
|
181
|
+
"kwargs": {...},
|
|
182
|
+
},
|
|
167
183
|
}
|
|
168
184
|
),
|
|
169
185
|
OpAny(
|
|
@@ -171,13 +187,19 @@ def main():
|
|
|
171
187
|
Op(
|
|
172
188
|
BaseOpEnum.json_parsing,
|
|
173
189
|
kwargs={
|
|
174
|
-
"file_system_manager": {
|
|
190
|
+
"file_system_manager": {
|
|
191
|
+
"path": ..., # Here can be your custom file system manager (as an example S3)
|
|
192
|
+
"kwargs": {...},
|
|
193
|
+
},
|
|
175
194
|
}
|
|
176
195
|
),
|
|
177
196
|
Op(
|
|
178
197
|
BaseOpEnum.csv_parsing,
|
|
179
198
|
kwargs={
|
|
180
|
-
"file_system_manager": {
|
|
199
|
+
"file_system_manager": {
|
|
200
|
+
"path": ..., # Here can be your custom file system manager (as an example S3)
|
|
201
|
+
"kwargs": {...},
|
|
202
|
+
},,
|
|
181
203
|
"white_exceptions": [
|
|
182
204
|
TypeError,
|
|
183
205
|
BaseExceptionsEnum.csv_parsing_error,
|
|
@@ -213,8 +235,10 @@ The library is developer-friendly, making it easy to extend and integrate custom
|
|
|
213
235
|
|
|
214
236
|
```python
|
|
215
237
|
from pipelet.processors.base import BaseProcessor
|
|
238
|
+
from pipelet.decorators import logging_decorator
|
|
216
239
|
|
|
217
240
|
class CustomProcessor(BaseProcessor):
|
|
241
|
+
@logging_decorator(custom_logger=...) # If you need this you can use either a custom logger or a default logger
|
|
218
242
|
def process(self, input_data):
|
|
219
243
|
# Custom data processing logic
|
|
220
244
|
yield transformed_data
|
|
@@ -49,10 +49,14 @@ A processor for parsing CSV files using the `pandas` library. It supports numero
|
|
|
49
49
|
### 7. **JsonParser**
|
|
50
50
|
A processor for parsing JSON data from strings or files into Python dictionaries. It supports configuring how numbers, constants (e.g., NaN), and JSON objects are handled with custom functions.
|
|
51
51
|
|
|
52
|
-
### 8. **
|
|
52
|
+
### 8. **UnzipProcessor**
|
|
53
53
|
A processor for extracting files from `.zip` archives. It supports extracting data into files, managing chunk sizes, and deleting the original archive after processing.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
|
|
56
|
+
### 9. **TarExtractProcessor**
|
|
57
|
+
A processor for extracting files from `".tar", ".gz", ".bz2", ".xz", ".tgz", ".tbz2"` archives. It supports extracting data into files, managing chunk sizes, and deleting the original archive after processing.
|
|
58
|
+
|
|
59
|
+
### 10. **RetryProcessor**
|
|
56
60
|
|
|
57
61
|
A processor designed to handle transient errors by retrying operations based on a customizable retry policy.
|
|
58
62
|
|
|
@@ -71,7 +75,7 @@ A processor designed to handle transient errors by retrying operations based on
|
|
|
71
75
|
from pipelet.processors.http import HttpDataExtractProcessor
|
|
72
76
|
from pipelet.processors.csv_parser import CsvParser
|
|
73
77
|
from pipelet.processors.json_parser import JsonParser
|
|
74
|
-
from pipelet.processors.unzip import
|
|
78
|
+
from pipelet.processors.unzip import UnzipProcessor
|
|
75
79
|
from pipelet.processors.file_system import AbstractFileSystemManager
|
|
76
80
|
|
|
77
81
|
# Create file system manager
|
|
@@ -81,7 +85,7 @@ file_system_manager = AbstractFileSystemManager()
|
|
|
81
85
|
http_processor = HttpDataExtractProcessor(file_system_manager)
|
|
82
86
|
csv_parser = CsvParser(file_system_manager)
|
|
83
87
|
json_parser = JsonParser(file_system_manager)
|
|
84
|
-
unzip_processor =
|
|
88
|
+
unzip_processor = UnzipProcessor(file_system_manager)
|
|
85
89
|
|
|
86
90
|
# Create processor chain
|
|
87
91
|
http_processor.set_next(csv_parser).set_next(json_parser).set_next(unzip_processor)
|
|
@@ -98,7 +102,7 @@ for output in http_processor.process(input_data):
|
|
|
98
102
|
from pipelet.processors.http import HttpDataExtractProcessor
|
|
99
103
|
from pipelet.processors.csv_parser import CsvParser
|
|
100
104
|
from pipelet.processors.json_parser import JsonParser
|
|
101
|
-
from pipelet.processors.unzip import
|
|
105
|
+
from pipelet.processors.unzip import UnzipProcessor
|
|
102
106
|
from pipelet.processors.file_system import AbstractFileSystemManager
|
|
103
107
|
|
|
104
108
|
# Create file system manager
|
|
@@ -108,7 +112,7 @@ file_system_manager = AbstractFileSystemManager()
|
|
|
108
112
|
http_processor = HttpDataExtractProcessor(file_system_manager)
|
|
109
113
|
csv_parser = CsvParser(file_system_manager)
|
|
110
114
|
json_parser = JsonParser(file_system_manager)
|
|
111
|
-
unzip_processor =
|
|
115
|
+
unzip_processor = UnzipProcessor(file_system_manager)
|
|
112
116
|
|
|
113
117
|
# Create processor chain using `>>`
|
|
114
118
|
pipeline = http_processor >> csv_parser >> json_parser >> unzip_processor
|
|
@@ -127,23 +131,35 @@ from pipelet.processors.file_system import FileModeEnum
|
|
|
127
131
|
|
|
128
132
|
def main():
|
|
129
133
|
# Define the pipeline with parallel processing for parsing and unzipping
|
|
134
|
+
|
|
130
135
|
pipeline = [
|
|
131
136
|
Op(
|
|
132
137
|
BaseOpEnum.downloading,
|
|
133
138
|
kwargs={
|
|
134
|
-
"file_system_manager": {
|
|
139
|
+
"file_system_manager": {
|
|
140
|
+
"path": ..., # Here can be your custom file system manager (as an example S3)
|
|
141
|
+
"kwargs": {...},
|
|
142
|
+
},
|
|
135
143
|
"retry_args": {
|
|
144
|
+
"retry_processor": ..., # Here can be your custom retry processor
|
|
136
145
|
"max_retries": 3,
|
|
137
146
|
"delay": 1,
|
|
138
147
|
"delay_step": 1,
|
|
139
148
|
"retry_with_white_exc": True,
|
|
140
149
|
},
|
|
150
|
+
"logging_args": {
|
|
151
|
+
"logger": ..., # Here can be your custom logger
|
|
152
|
+
"logging": True, # Logging intup data
|
|
153
|
+
},
|
|
141
154
|
}
|
|
142
155
|
),
|
|
143
156
|
Op(
|
|
144
157
|
BaseOpEnum.upzip,
|
|
145
158
|
kwargs={
|
|
146
|
-
"file_system_manager": {
|
|
159
|
+
"file_system_manager": {
|
|
160
|
+
"path": ..., # Here can be your custom file system manager (as an example S3)
|
|
161
|
+
"kwargs": {...},
|
|
162
|
+
},
|
|
147
163
|
}
|
|
148
164
|
),
|
|
149
165
|
OpAny(
|
|
@@ -151,13 +167,19 @@ def main():
|
|
|
151
167
|
Op(
|
|
152
168
|
BaseOpEnum.json_parsing,
|
|
153
169
|
kwargs={
|
|
154
|
-
"file_system_manager": {
|
|
170
|
+
"file_system_manager": {
|
|
171
|
+
"path": ..., # Here can be your custom file system manager (as an example S3)
|
|
172
|
+
"kwargs": {...},
|
|
173
|
+
},
|
|
155
174
|
}
|
|
156
175
|
),
|
|
157
176
|
Op(
|
|
158
177
|
BaseOpEnum.csv_parsing,
|
|
159
178
|
kwargs={
|
|
160
|
-
"file_system_manager": {
|
|
179
|
+
"file_system_manager": {
|
|
180
|
+
"path": ..., # Here can be your custom file system manager (as an example S3)
|
|
181
|
+
"kwargs": {...},
|
|
182
|
+
},,
|
|
161
183
|
"white_exceptions": [
|
|
162
184
|
TypeError,
|
|
163
185
|
BaseExceptionsEnum.csv_parsing_error,
|
|
@@ -193,8 +215,10 @@ The library is developer-friendly, making it easy to extend and integrate custom
|
|
|
193
215
|
|
|
194
216
|
```python
|
|
195
217
|
from pipelet.processors.base import BaseProcessor
|
|
218
|
+
from pipelet.decorators import logging_decorator
|
|
196
219
|
|
|
197
220
|
class CustomProcessor(BaseProcessor):
|
|
221
|
+
@logging_decorator(custom_logger=...) # If you need this you can use either a custom logger or a default logger
|
|
198
222
|
def process(self, input_data):
|
|
199
223
|
# Custom data processing logic
|
|
200
224
|
yield transformed_data
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Any, Callable, Generator, Optional, Union
|
|
3
|
+
|
|
4
|
+
from pipelet.decarators.option_decorator import prepare_gen_processor
|
|
5
|
+
from pipelet.log import logger_factory
|
|
6
|
+
from pipelet.processors.base import BaseProcessor
|
|
7
|
+
|
|
8
|
+
default_logger = logger_factory()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def logging_decorator(
|
|
12
|
+
func: Callable[
|
|
13
|
+
...,
|
|
14
|
+
Generator[Any, Any, Optional[Any]],
|
|
15
|
+
],
|
|
16
|
+
custom_logger: Optional[Any] = None,
|
|
17
|
+
) -> Callable[
|
|
18
|
+
...,
|
|
19
|
+
Generator[Any, Any, Optional[Any]],
|
|
20
|
+
]:
|
|
21
|
+
"""
|
|
22
|
+
A decorator that adds logging functionality to a processor's `process` method.
|
|
23
|
+
|
|
24
|
+
Logs the processor's class name, arguments, and keyword arguments each time
|
|
25
|
+
the `process` method is invoked. Supports generator functions and normalizes
|
|
26
|
+
calls to ensure compatibility with bound and unbound methods.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
func (Callable): The generator function (`process` method) to wrap.
|
|
30
|
+
custom_logger (Optional[Any]): Custom logger to use. Defaults to the global logger.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Callable: A wrapped generator function with logging functionality.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
```
|
|
37
|
+
@logging_decorator
|
|
38
|
+
def process(self, *args, **kwargs):
|
|
39
|
+
yield ...
|
|
40
|
+
```
|
|
41
|
+
"""
|
|
42
|
+
logger_to_use = (
|
|
43
|
+
custom_logger or default_logger
|
|
44
|
+
) # Используем кастомный логгер, если он передан
|
|
45
|
+
|
|
46
|
+
@wraps(func)
|
|
47
|
+
def wrapper(
|
|
48
|
+
obj: BaseProcessor[Any, Any, Any, Any],
|
|
49
|
+
*args,
|
|
50
|
+
**kwargs,
|
|
51
|
+
) -> Generator[
|
|
52
|
+
Any,
|
|
53
|
+
Any,
|
|
54
|
+
Union[Any, None],
|
|
55
|
+
]:
|
|
56
|
+
logger_to_use.info(
|
|
57
|
+
f"Processor: {obj.__class__.__name__}, args: {args}, kwargs: {kwargs}"
|
|
58
|
+
)
|
|
59
|
+
generator = prepare_gen_processor(
|
|
60
|
+
func, obj, *args, **kwargs
|
|
61
|
+
) # Normalized call
|
|
62
|
+
while True:
|
|
63
|
+
yield next(generator)
|
|
64
|
+
|
|
65
|
+
return wrapper
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import types
|
|
2
|
+
from typing import Any, Callable, Generator
|
|
3
|
+
|
|
4
|
+
from pipelet.log import logger_factory
|
|
5
|
+
from pipelet.processors.base import BaseProcessor
|
|
6
|
+
|
|
7
|
+
logger = logger_factory()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def prepare_gen_processor(
|
|
11
|
+
func: Callable[..., Generator[Any, Any, Any]],
|
|
12
|
+
obj: BaseProcessor[Any, Any, Any, Any],
|
|
13
|
+
*args,
|
|
14
|
+
**kwargs
|
|
15
|
+
) -> Generator[Any, Any, Any]:
|
|
16
|
+
"""
|
|
17
|
+
A utility function to prepare and execute generator-based processors.
|
|
18
|
+
|
|
19
|
+
This function normalizes the invocation of generator functions or methods.
|
|
20
|
+
If the provided callable `func` is a bound method, it is invoked directly
|
|
21
|
+
with `*args` and `**kwargs`. If `func` is an unbound function, it is
|
|
22
|
+
invoked with `obj` passed as the first argument along with `*args` and `**kwargs`.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
func (Callable): The generator function or method to be executed.
|
|
26
|
+
obj (BaseProcessor): The processor object, passed explicitly for unbound functions.
|
|
27
|
+
*args: Positional arguments to pass to the generator.
|
|
28
|
+
**kwargs: Keyword arguments to pass to the generator.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Generator: The generator object produced by executing `func`.
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
if isinstance(func, types.MethodType):
|
|
35
|
+
return func(*args, **kwargs)
|
|
36
|
+
return func(obj, *args, **kwargs)
|
|
@@ -102,7 +102,6 @@ class HttpDataExtractProcessor(
|
|
|
102
102
|
Yields:
|
|
103
103
|
PathLike | str: The file name or path of the downloaded file.
|
|
104
104
|
"""
|
|
105
|
-
logger.debug(f"Starting download from {input_data}")
|
|
106
105
|
resp = httpx.get(
|
|
107
106
|
input_data,
|
|
108
107
|
params=self._params,
|
|
@@ -125,7 +124,6 @@ class HttpDataExtractProcessor(
|
|
|
125
124
|
resp.content,
|
|
126
125
|
)
|
|
127
126
|
|
|
128
|
-
logger.debug("Download was successful")
|
|
129
127
|
if self._next is not None:
|
|
130
128
|
yield from self._next.process(file_name)
|
|
131
129
|
else:
|
|
@@ -218,7 +216,6 @@ class HttpxStreamDownloadProcessor(
|
|
|
218
216
|
Yields:
|
|
219
217
|
PathLike[str] | str: The file path of the downloaded file.
|
|
220
218
|
"""
|
|
221
|
-
logger.debug(f"Starting download from {input_data}")
|
|
222
219
|
with httpx.stream(
|
|
223
220
|
method=self._method,
|
|
224
221
|
url=input_data,
|
|
@@ -238,7 +235,6 @@ class HttpxStreamDownloadProcessor(
|
|
|
238
235
|
if chunk:
|
|
239
236
|
self._file_system_manager.append_to_file(file_path, chunk)
|
|
240
237
|
|
|
241
|
-
logger.debug(f"Download was successful: {file_path}")
|
|
242
238
|
if self._next is not None:
|
|
243
239
|
yield from self._next.process(file_path)
|
|
244
240
|
else:
|
|
@@ -227,7 +227,6 @@ class CsvParser(
|
|
|
227
227
|
Raises:
|
|
228
228
|
CsvParsingError: If parsing fails.
|
|
229
229
|
"""
|
|
230
|
-
logger.debug(f"Start parsing: {input_data}")
|
|
231
230
|
suffix = Path(input_data).suffix
|
|
232
231
|
if suffix != ".csv":
|
|
233
232
|
raise CsvParsingError(
|
|
@@ -292,7 +291,6 @@ class CsvParser(
|
|
|
292
291
|
yield records
|
|
293
292
|
if self._auto_delete:
|
|
294
293
|
self._file_system_manager.delete_file(input_data)
|
|
295
|
-
logger.debug(f"Successful parsing: {input_data}")
|
|
296
294
|
|
|
297
295
|
def __str__(self) -> str:
|
|
298
296
|
return (
|
|
@@ -90,12 +90,10 @@ class JsonParser(
|
|
|
90
90
|
"""
|
|
91
91
|
# Read content using the file system manager
|
|
92
92
|
content = self._file_system_manager.read_file(input_data)
|
|
93
|
-
logger.debug("Read content from %s", input_data)
|
|
94
93
|
|
|
95
94
|
# Parse the JSON content with custom arguments
|
|
96
95
|
try:
|
|
97
96
|
data = json.loads(content, **self._parse_kwargs)
|
|
98
|
-
logger.debug(f"JSON {input_data} has parsed successfully")
|
|
99
97
|
except json.JSONDecodeError as e:
|
|
100
98
|
logger.error("Failed to parse JSON: %s", e)
|
|
101
99
|
raise
|
|
@@ -15,7 +15,7 @@ from pipelet.processors.file_system import (
|
|
|
15
15
|
logger = logger_factory()
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class
|
|
18
|
+
class UnzipProcessor(
|
|
19
19
|
BaseProcessor[
|
|
20
20
|
Union[str, PathLike[str]], Union[str, PathLike[str]], None, None
|
|
21
21
|
],
|
|
@@ -45,7 +45,7 @@ class BaseUnzipProcessor(
|
|
|
45
45
|
batch_size: Optional[int] = None,
|
|
46
46
|
) -> None:
|
|
47
47
|
"""
|
|
48
|
-
Initializes the
|
|
48
|
+
Initializes the UnzipProcessor.
|
|
49
49
|
|
|
50
50
|
Args:
|
|
51
51
|
file_system_manager (AbstractFileSystemManager): The file system manager for file operations.
|
|
@@ -84,7 +84,6 @@ class BaseUnzipProcessor(
|
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
file_path = self._file_system_manager.get_path(input_data)
|
|
87
|
-
logger.debug(f"Starting to unzip {input_data}")
|
|
88
87
|
|
|
89
88
|
unzip_files: List[Union[str, PathLike[str]]] = []
|
|
90
89
|
with zipfile.ZipFile(file_path, FileModeEnum.READ) as zip_ref:
|
|
@@ -107,8 +106,6 @@ class BaseUnzipProcessor(
|
|
|
107
106
|
|
|
108
107
|
unzip_files.append(extracted_file_path)
|
|
109
108
|
|
|
110
|
-
logger.debug("Unzipping was successful")
|
|
111
|
-
|
|
112
109
|
if self._next is not None:
|
|
113
110
|
for item in unzip_files:
|
|
114
111
|
gen = self._next.process(item)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import tarfile
|
|
2
|
+
from os import PathLike
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Generator, List, Optional, Type, Union
|
|
5
|
+
|
|
6
|
+
from pipelet.exceptions.base import ProcessorStopIteration
|
|
7
|
+
from pipelet.exceptions.unzip_processor import UnzipError
|
|
8
|
+
from pipelet.log import logger_factory
|
|
9
|
+
from pipelet.processors.base import BaseProcessor
|
|
10
|
+
from pipelet.processors.file_system import (
|
|
11
|
+
AbstractFileSystemManager,
|
|
12
|
+
FileModeEnum,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
logger = logger_factory()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TarExtractProcessor(
|
|
19
|
+
BaseProcessor[
|
|
20
|
+
Union[str, PathLike[str]], Union[str, PathLike[str]], None, None
|
|
21
|
+
],
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
A processor that handles extracting `.tar` archives (including compressed formats).
|
|
25
|
+
|
|
26
|
+
This processor takes a `.tar` file (or compressed `.tar` variants like `.tar.gz`, `.tgz`),
|
|
27
|
+
extracts its contents, and yields the paths of the extracted files and directories.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
_file_system_manager (AbstractFileSystemManager): File system manager for file operations.
|
|
31
|
+
_white_exceptions (List[Type[Exception]]): List of exceptions to bypass.
|
|
32
|
+
_auto_delete (bool): Flag to delete the tar file after processing.
|
|
33
|
+
_batch_size (Optional[int]): The batch size in bytes for extracting large files.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
file_system_manager: AbstractFileSystemManager[
|
|
39
|
+
Union[str, PathLike[str]], Union[str, bytes]
|
|
40
|
+
],
|
|
41
|
+
white_exceptions: Optional[List[Type[Exception]]] = None,
|
|
42
|
+
auto_delete: bool = True,
|
|
43
|
+
batch_size: Optional[int] = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
self._file_system_manager = file_system_manager
|
|
46
|
+
self._white_exceptions = white_exceptions or []
|
|
47
|
+
self._auto_delete = auto_delete
|
|
48
|
+
self._batch_size = (
|
|
49
|
+
batch_size * 1024 * 1024 if batch_size is not None else None
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def process(
|
|
53
|
+
self,
|
|
54
|
+
input_data: Union[str, PathLike[str]],
|
|
55
|
+
) -> Generator[Union[str, PathLike[str]], None, None]:
|
|
56
|
+
"""
|
|
57
|
+
Processes the given tar file and extracts its contents.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
input_data (Union[str, PathLike[str]]): The path to the tar file to extract.
|
|
61
|
+
|
|
62
|
+
Yields:
|
|
63
|
+
Union[str, PathLike[str]]: The path to each extracted file or directory.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
UnzipError: If the file is not a `.tar` file or cannot be processed.
|
|
67
|
+
"""
|
|
68
|
+
suffix = Path(input_data).suffix
|
|
69
|
+
if suffix not in [".tar", ".gz", ".bz2", ".xz", ".tgz", ".tbz2"]:
|
|
70
|
+
raise UnzipError(
|
|
71
|
+
input_data,
|
|
72
|
+
f"File has an extension '{suffix}', expected a supported .tar format",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
file_path = self._file_system_manager.get_path(input_data)
|
|
76
|
+
extracted_paths: List[Union[str, PathLike[str]]] = []
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
with tarfile.open(file_path, mode="r:*") as tar:
|
|
80
|
+
for member in tar.getmembers():
|
|
81
|
+
extracted_path = self._file_system_manager.get_path(
|
|
82
|
+
member.name
|
|
83
|
+
)
|
|
84
|
+
if member.isdir():
|
|
85
|
+
# Create the directory if it does not exist
|
|
86
|
+
Path(extracted_path).mkdir(parents=True, exist_ok=True)
|
|
87
|
+
logger.info(f"Created directory: {member.name}")
|
|
88
|
+
extracted_paths.append(extracted_path)
|
|
89
|
+
elif member.isfile():
|
|
90
|
+
# Extract file and handle large files with batching
|
|
91
|
+
source_file = tar.extractfile(member)
|
|
92
|
+
if source_file is not None:
|
|
93
|
+
with source_file, open(
|
|
94
|
+
extracted_path, FileModeEnum.WRITE_BINARY
|
|
95
|
+
) as target_file:
|
|
96
|
+
if self._batch_size is not None:
|
|
97
|
+
while chunk := source_file.read(
|
|
98
|
+
self._batch_size
|
|
99
|
+
):
|
|
100
|
+
target_file.write(chunk)
|
|
101
|
+
else:
|
|
102
|
+
target_file.write(source_file.read())
|
|
103
|
+
extracted_paths.append(extracted_path)
|
|
104
|
+
else:
|
|
105
|
+
logger.warning(
|
|
106
|
+
f"Could not extract file: {member.name}"
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
logger.info(
|
|
110
|
+
f"Skipping unsupported member type: {member.name}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
raise UnzipError(file_path, f"Failed to extract tar file: {e}")
|
|
115
|
+
|
|
116
|
+
# Pass extracted paths to the next processor, if defined
|
|
117
|
+
if self._next is not None:
|
|
118
|
+
for item in extracted_paths:
|
|
119
|
+
try:
|
|
120
|
+
yield from self._next.process(item)
|
|
121
|
+
except ProcessorStopIteration:
|
|
122
|
+
pass
|
|
123
|
+
else:
|
|
124
|
+
yield from iter(extracted_paths)
|
|
125
|
+
|
|
126
|
+
# Optionally delete the tar file after processing
|
|
127
|
+
if self._auto_delete:
|
|
128
|
+
self._file_system_manager.delete_file(input_data)
|
|
129
|
+
|
|
130
|
+
def __str__(self) -> str:
|
|
131
|
+
return (
|
|
132
|
+
f"{self.__class__.__name__}"
|
|
133
|
+
f"(auto_delete={self._auto_delete}, "
|
|
134
|
+
f"batch_size={self._batch_size})"
|
|
135
|
+
)
|
|
@@ -24,7 +24,7 @@ Classes and Functions:
|
|
|
24
24
|
- `BaseOpEnum`: Enum defining paths to processor classes for common operations.
|
|
25
25
|
- `Op`: Represents a single operation configuration.
|
|
26
26
|
- `OpAny`, `OpAll`: Define composite operations requiring any or all conditions.
|
|
27
|
-
- `
|
|
27
|
+
- `prepare_retry_option`: Wraps a option with retry logic.
|
|
28
28
|
- `process_op`, `process_op_composite`: Handle individual and composite operation processing.
|
|
29
29
|
- `get_processor_for_operation`: Dispatch function for retrieving the appropriate processor.
|
|
30
30
|
|
|
@@ -37,10 +37,12 @@ This framework enables modular, reusable, and extensible ETL workflows by abstra
|
|
|
37
37
|
processing logic into well-defined processors and operations.
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
+
import types
|
|
40
41
|
from dataclasses import dataclass, field
|
|
41
42
|
from enum import Enum
|
|
42
43
|
from typing import Any, Callable, Dict, Generic, List, Type, TypeVar, Union
|
|
43
44
|
|
|
45
|
+
from pipelet.decarators.logging_decorator import logging_decorator
|
|
44
46
|
from pipelet.factories import KEYWORD_FACTORY_MAP
|
|
45
47
|
from pipelet.log import logger_factory
|
|
46
48
|
from pipelet.processors import get_processor
|
|
@@ -59,7 +61,10 @@ class BaseOpEnum(str, Enum):
|
|
|
59
61
|
|
|
60
62
|
downloading = "pipelet.etl.extract.http.HttpDataExtractProcessor"
|
|
61
63
|
stream_downloading = "pipelet.etl.extract.http.HttpxStreamDownloadProcessor"
|
|
62
|
-
unzip = "pipelet.etl.transform.unzip.
|
|
64
|
+
unzip = "pipelet.etl.transform.unzip.UnzipProcessor"
|
|
65
|
+
tar_extract = (
|
|
66
|
+
"pipelet.etl.transform.unzip.tar_processor.TarExtractProcessor"
|
|
67
|
+
)
|
|
63
68
|
csv_parsing = "pipelet.etl.transform.csv.CsvParser"
|
|
64
69
|
json_parsing = "pipelet.etl.transform.json.JsonParser"
|
|
65
70
|
retrying = "pipelet.processors.retry_processor.RetryProcessor"
|
|
@@ -75,7 +80,7 @@ class Op:
|
|
|
75
80
|
Represents an operation configuration.
|
|
76
81
|
|
|
77
82
|
Attributes:
|
|
78
|
-
operation (str): Path to the processor class in "module.
|
|
83
|
+
operation (str): Path to the processor class in "module.Classmeta_option_decoratorName" format.
|
|
79
84
|
"""
|
|
80
85
|
|
|
81
86
|
operation: str
|
|
@@ -115,7 +120,7 @@ class OpAll(BaseOpComposite[Union["OpAny", "OpAll"]]):
|
|
|
115
120
|
"""
|
|
116
121
|
|
|
117
122
|
|
|
118
|
-
def
|
|
123
|
+
def prepare_retry_option(
|
|
119
124
|
processor: BaseProcessor[Any, Any, Any, Any], kwargs: Dict[str, Any]
|
|
120
125
|
) -> BaseProcessor[Any, Any, Any, Any]:
|
|
121
126
|
"""
|
|
@@ -149,6 +154,79 @@ def prepare_retry_processor(
|
|
|
149
154
|
return processor
|
|
150
155
|
|
|
151
156
|
|
|
157
|
+
def prepare_logging_option(
|
|
158
|
+
processor: BaseProcessor[Any, Any, Any, Any], kwargs: Dict[str, Any]
|
|
159
|
+
) -> BaseProcessor[Any, Any, Any, Any]:
|
|
160
|
+
"""
|
|
161
|
+
Prepares and applies logging functionality to the processor.
|
|
162
|
+
|
|
163
|
+
This function checks for a `logging` flag in the provided `kwargs`. If `logging` is enabled,
|
|
164
|
+
it wraps the `process` method of the processor with the `logging_decorator`.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
processor (BaseProcessor): The processor instance to which logging functionality will be added.
|
|
168
|
+
kwargs (Dict[str, Any]): A dictionary of arguments, including the `logging` flag.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
BaseProcessor: The processor with logging functionality applied if enabled.
|
|
172
|
+
"""
|
|
173
|
+
logging = kwargs.pop("logging", False)
|
|
174
|
+
logger = kwargs.pop("logger", None)
|
|
175
|
+
if logging:
|
|
176
|
+
processor.process = types.MethodType(
|
|
177
|
+
logging_decorator(processor.process, logger), processor
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return processor
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
OPTION_TO_HANDLER_MAP: Dict[
|
|
184
|
+
str,
|
|
185
|
+
Callable[
|
|
186
|
+
[BaseProcessor[Any, Any, Any, Any], Dict[str, Any]],
|
|
187
|
+
BaseProcessor[Any, Any, Any, Any],
|
|
188
|
+
],
|
|
189
|
+
] = {
|
|
190
|
+
"logging_args": prepare_logging_option,
|
|
191
|
+
"retry_args": prepare_retry_option,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def prepare_additional_options(
|
|
196
|
+
processor: BaseProcessor[Any, Any, Any, Any],
|
|
197
|
+
kwargs: Dict[str, Dict[str, Any]],
|
|
198
|
+
) -> BaseProcessor[Any, Any, Any, Any]:
|
|
199
|
+
"""
|
|
200
|
+
Prepares and applies additional options to the processor.
|
|
201
|
+
|
|
202
|
+
This function processes a dictionary of option names and their associated arguments,
|
|
203
|
+
applying each option to the processor using a corresponding handler function from
|
|
204
|
+
the `OPTION_TO_HANDLER_MAP`.
|
|
205
|
+
|
|
206
|
+
Supported options:
|
|
207
|
+
- `logging_args`: Configures logging functionality.
|
|
208
|
+
- `retry_args`: Configures retry logic.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
processor (BaseProcessor): The processor instance to which additional options will be applied.
|
|
212
|
+
kwargs (Dict[str, Dict[str, Any]]): A dictionary where keys are option names
|
|
213
|
+
and values are their respective argument dictionaries.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
BaseProcessor: The processor with the additional options applied.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
KeyError: If an unsupported option name is provided in `kwargs`.
|
|
220
|
+
"""
|
|
221
|
+
if kwargs:
|
|
222
|
+
for option_name, option_kwargs in kwargs.items():
|
|
223
|
+
option_handler = OPTION_TO_HANDLER_MAP.get(option_name, None)
|
|
224
|
+
if option_handler is None:
|
|
225
|
+
raise KeyError(f"Option {option_name} isn't supported")
|
|
226
|
+
processor = option_handler(processor, option_kwargs)
|
|
227
|
+
return processor
|
|
228
|
+
|
|
229
|
+
|
|
152
230
|
def process_op(op: Op) -> BaseProcessor[Any, Any, Any, Any]:
|
|
153
231
|
"""
|
|
154
232
|
Processes an Op and returns an instance of BaseProcessor.
|
|
@@ -164,15 +242,19 @@ def process_op(op: Op) -> BaseProcessor[Any, Any, Any, Any]:
|
|
|
164
242
|
"""
|
|
165
243
|
type_processor = get_processor(op.operation)
|
|
166
244
|
kwargs: Dict[str, Any] = {}
|
|
245
|
+
kwargs_to_additional_processors: Dict[str, Any] = {}
|
|
167
246
|
for k, v in op.kwargs.items():
|
|
168
247
|
factory = KEYWORD_FACTORY_MAP.get(k, None)
|
|
169
248
|
if factory:
|
|
170
249
|
kwargs[k] = factory(**v) if isinstance(v, dict) else factory(v)
|
|
171
|
-
|
|
250
|
+
elif OPTION_TO_HANDLER_MAP.get(k, None) is None:
|
|
172
251
|
kwargs[k] = v
|
|
173
|
-
|
|
252
|
+
else:
|
|
253
|
+
kwargs_to_additional_processors[k] = v
|
|
174
254
|
processor = type_processor(**kwargs)
|
|
175
|
-
processor =
|
|
255
|
+
processor = prepare_additional_options(
|
|
256
|
+
processor, kwargs_to_additional_processors
|
|
257
|
+
)
|
|
176
258
|
return processor
|
|
177
259
|
|
|
178
260
|
|
|
@@ -21,6 +21,7 @@ Key Features:
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
from abc import ABCMeta
|
|
24
|
+
from functools import wraps
|
|
24
25
|
from typing import Any, Callable, Generator, List, Optional, Type, Union
|
|
25
26
|
|
|
26
27
|
from pipelet.exceptions.base import (
|
|
@@ -68,6 +69,7 @@ class ProcessorMeta(ABCMeta):
|
|
|
68
69
|
Callable: A wrapped generator function with added exception handling.
|
|
69
70
|
"""
|
|
70
71
|
|
|
72
|
+
@wraps(func)
|
|
71
73
|
def wrapper(
|
|
72
74
|
obj: AbstractProcessor,
|
|
73
75
|
*args,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|