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.
Files changed (50) hide show
  1. {pipelet-0.0.2 → pipelet-0.0.4}/PKG-INFO +35 -11
  2. {pipelet-0.0.2 → pipelet-0.0.4}/README.md +34 -10
  3. pipelet-0.0.4/pipelet/decarators/__init__.py +3 -0
  4. pipelet-0.0.4/pipelet/decarators/logging_decorator.py +65 -0
  5. pipelet-0.0.4/pipelet/decarators/option_decorator.py +36 -0
  6. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/extract/http/http_extractor.py +0 -4
  7. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/csv/reader.py +0 -2
  8. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/json/reader.py +0 -2
  9. pipelet-0.0.4/pipelet/etl/transform/unzip/__init__.py +7 -0
  10. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/unzip/base.py +2 -5
  11. pipelet-0.0.4/pipelet/etl/transform/unzip/tar_processor.py +135 -0
  12. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/operations/operations.py +89 -7
  13. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/meta.py +2 -0
  14. {pipelet-0.0.2 → pipelet-0.0.4}/pyproject.toml +1 -1
  15. pipelet-0.0.2/pipelet/etl/transform/unzip/__init__.py +0 -3
  16. {pipelet-0.0.2 → pipelet-0.0.4}/LICENSE +0 -0
  17. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/__init__.py +0 -0
  18. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/__init__.py +0 -0
  19. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/extract/__init__.py +0 -0
  20. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/extract/http/__init__.py +0 -0
  21. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/__init__.py +0 -0
  22. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/csv/__init__.py +0 -0
  23. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/etl/transform/json/__init__.py +0 -0
  24. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/__init__.py +0 -0
  25. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/base.py +0 -0
  26. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/csv_processor.py +0 -0
  27. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/download_processor.py +0 -0
  28. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/file_system.py +0 -0
  29. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/exceptions/unzip_processor.py +0 -0
  30. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/factories/__init__.py +0 -0
  31. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/factories/exceptions_factory.py +0 -0
  32. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/factories/file_system_factory.py +0 -0
  33. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/log/__init__.py +0 -0
  34. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/log/logger.py +0 -0
  35. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/operations/__init__.py +0 -0
  36. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/operations/pipeline.py +0 -0
  37. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/__init__.py +0 -0
  38. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/abc.py +0 -0
  39. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/base.py +0 -0
  40. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/chain_processors/__init__.py +0 -0
  41. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/chain_processors/base.py +0 -0
  42. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/file_system/__init__.py +0 -0
  43. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/file_system/base.py +0 -0
  44. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/file_system/file_system_types.py +0 -0
  45. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/mixins.py +0 -0
  46. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/processor_types.py +0 -0
  47. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/retry_processor.py +0 -0
  48. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/processors/splitter_processor.py +0 -0
  49. {pipelet-0.0.2 → pipelet-0.0.4}/pipelet/utils/__init__.py +0 -0
  50. {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.2
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. **BaseUnzipProcessor**
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
- ### 9. **RetryProcessor**
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 BaseUnzipProcessor
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 = BaseUnzipProcessor(file_system_manager)
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 BaseUnzipProcessor
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 = BaseUnzipProcessor(file_system_manager)
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. **BaseUnzipProcessor**
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
- ### 9. **RetryProcessor**
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 BaseUnzipProcessor
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 = BaseUnzipProcessor(file_system_manager)
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 BaseUnzipProcessor
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 = BaseUnzipProcessor(file_system_manager)
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,3 @@
1
+ from pipelet.decarators.logging_decorator import logging_decorator
2
+
3
+ __all__ = ("logging_decorator",)
@@ -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
@@ -0,0 +1,7 @@
1
+ from pipelet.etl.transform.unzip.base import UnzipProcessor
2
+ from pipelet.etl.transform.unzip.tar_processor import TarExtractProcessor
3
+
4
+ __all__ = (
5
+ "UnzipProcessor",
6
+ "TarExtractProcessor",
7
+ )
@@ -15,7 +15,7 @@ from pipelet.processors.file_system import (
15
15
  logger = logger_factory()
16
16
 
17
17
 
18
- class BaseUnzipProcessor(
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 BaseUnzipProcessor.
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
- - `prepare_retry_processor`: Wraps a processor with retry logic.
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.BaseUnzipProcessor"
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.ClassName" format.
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 prepare_retry_processor(
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
- else:
250
+ elif OPTION_TO_HANDLER_MAP.get(k, None) is None:
172
251
  kwargs[k] = v
173
- retry_args: Dict[str, Any] = kwargs.pop("retry_args", {})
252
+ else:
253
+ kwargs_to_additional_processors[k] = v
174
254
  processor = type_processor(**kwargs)
175
- processor = prepare_retry_processor(processor, kwargs=retry_args)
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,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pipelet"
3
- version = "0.0.2"
3
+ version = "0.0.4"
4
4
  description = ""
5
5
  authors = ["Daniel Naumowich <cskteam4@gmail.com>"]
6
6
  readme = "README.md"
@@ -1,3 +0,0 @@
1
- from pipelet.etl.transform.unzip.base import BaseUnzipProcessor
2
-
3
- __all__ = ("BaseUnzipProcessor",)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes