splurge-dsv 2025.1.1__py3-none-any.whl → 2025.1.2__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.
@@ -11,19 +11,21 @@ Please preserve this header and all related material when sharing!
11
11
  This module is licensed under the MIT License.
12
12
  """
13
13
 
14
+ # Standard library imports
15
+ from collections.abc import Iterator
14
16
  from contextlib import contextmanager
15
17
  from pathlib import Path
16
- from typing import Iterator, Any, IO
18
+ from typing import IO, Any
17
19
 
20
+ # Local imports
18
21
  from splurge_dsv.exceptions import (
19
- SplurgeResourceAcquisitionError,
20
- SplurgeResourceReleaseError,
22
+ SplurgeFileEncodingError,
21
23
  SplurgeFileNotFoundError,
22
24
  SplurgeFilePermissionError,
23
- SplurgeFileEncodingError
25
+ SplurgeResourceAcquisitionError,
26
+ SplurgeResourceReleaseError,
24
27
  )
25
28
  from splurge_dsv.path_validator import PathValidator
26
-
27
29
 
28
30
  # Module-level constants for resource management
29
31
  DEFAULT_BUFFERING = -1 # Default buffering for file operations
@@ -38,14 +40,14 @@ def _safe_open_file(
38
40
  encoding: str | None = None,
39
41
  errors: str | None = None,
40
42
  newline: str | None = None,
41
- buffering: int = DEFAULT_BUFFERING
43
+ buffering: int = DEFAULT_BUFFERING,
42
44
  ) -> IO[Any]:
43
45
  """
44
46
  Safely open a file with proper error handling.
45
-
47
+
46
48
  This function provides centralized file opening with consistent error handling
47
49
  that converts standard file operation exceptions to custom exceptions.
48
-
50
+
49
51
  Args:
50
52
  file_path: Path to the file
51
53
  mode: File open mode
@@ -53,10 +55,10 @@ def _safe_open_file(
53
55
  errors: Error handling for encoding
54
56
  newline: Newline handling
55
57
  buffering: Buffer size
56
-
58
+
57
59
  Returns:
58
60
  File handle
59
-
61
+
60
62
  Raises:
61
63
  SplurgeFileNotFoundError: If file is not found
62
64
  SplurgeFilePermissionError: If permission is denied
@@ -64,75 +66,51 @@ def _safe_open_file(
64
66
  SplurgeResourceAcquisitionError: If other file operation fails
65
67
  """
66
68
  try:
67
- if 'b' in mode:
69
+ if "b" in mode:
68
70
  # Binary mode
69
- return open(
70
- file_path,
71
- mode=mode,
72
- buffering=buffering
73
- )
71
+ return open(file_path, mode=mode, buffering=buffering)
74
72
  else:
75
73
  # Text mode
76
- return open(
77
- file_path,
78
- mode=mode,
79
- encoding=encoding,
80
- errors=errors,
81
- newline=newline,
82
- buffering=buffering
83
- )
74
+ return open(file_path, mode=mode, encoding=encoding, errors=errors, newline=newline, buffering=buffering)
84
75
  except FileNotFoundError as e:
85
- raise SplurgeFileNotFoundError(
86
- f"File not found: {file_path}",
87
- details=str(e)
88
- )
76
+ raise SplurgeFileNotFoundError(f"File not found: {file_path}", details=str(e)) from e
89
77
  except PermissionError as e:
90
- raise SplurgeFilePermissionError(
91
- f"Permission denied: {file_path}",
92
- details=str(e)
93
- )
78
+ raise SplurgeFilePermissionError(f"Permission denied: {file_path}", details=str(e)) from e
94
79
  except UnicodeDecodeError as e:
95
- raise SplurgeFileEncodingError(
96
- f"Encoding error reading file: {file_path}",
97
- details=str(e)
98
- )
80
+ raise SplurgeFileEncodingError(f"Encoding error reading file: {file_path}", details=str(e)) from e
99
81
  except OSError as e:
100
- raise SplurgeResourceAcquisitionError(
101
- f"Failed to open file: {file_path}",
102
- details=str(e)
103
- )
82
+ raise SplurgeResourceAcquisitionError(f"Failed to open file: {file_path}", details=str(e)) from e
104
83
 
105
84
 
106
85
  class ResourceManager:
107
86
  """
108
87
  Generic resource manager that implements the ResourceManagerProtocol.
109
-
88
+
110
89
  This class provides a base implementation for resource management
111
90
  with acquire/release semantics.
112
91
  """
113
-
92
+
114
93
  def __init__(self) -> None:
115
94
  """Initialize the resource manager."""
116
95
  self._resource: Any | None = None
117
96
  self._is_acquired_flag: bool = False
118
-
97
+
119
98
  def acquire(self) -> Any:
120
99
  """
121
100
  Acquire the managed resource.
122
-
101
+
123
102
  Returns:
124
103
  The acquired resource
125
-
104
+
126
105
  Raises:
127
106
  NotImplementedError: If _create_resource is not implemented by subclass
128
107
  SplurgeResourceAcquisitionError: If resource cannot be acquired
129
108
  """
130
109
  if self._is_acquired_flag:
131
110
  raise SplurgeResourceAcquisitionError(
132
- "Resource is already acquired",
133
- details="Cannot acquire resource that is already in use"
111
+ "Resource is already acquired", details="Cannot acquire resource that is already in use"
134
112
  )
135
-
113
+
136
114
  try:
137
115
  self._resource = self._create_resource()
138
116
  self._is_acquired_flag = True
@@ -141,74 +119,68 @@ class ResourceManager:
141
119
  # Re-raise NotImplementedError without wrapping it
142
120
  raise
143
121
  except Exception as e:
144
- raise SplurgeResourceAcquisitionError(
145
- "Failed to acquire resource",
146
- details=str(e)
147
- )
148
-
122
+ raise SplurgeResourceAcquisitionError("Failed to acquire resource", details=str(e)) from e
123
+
149
124
  def release(self) -> None:
150
125
  """
151
126
  Release the managed resource.
152
-
127
+
153
128
  Raises:
154
129
  SplurgeResourceReleaseError: If resource cannot be released
155
130
  """
156
131
  if not self._is_acquired_flag:
157
132
  return # Nothing to release
158
-
133
+
159
134
  try:
160
135
  self._cleanup_resource()
161
136
  self._resource = None
162
137
  self._is_acquired_flag = False
163
138
  except Exception as e:
164
- raise SplurgeResourceReleaseError(
165
- "Failed to release resource",
166
- details=str(e)
167
- )
168
-
139
+ raise SplurgeResourceReleaseError("Failed to release resource", details=str(e)) from e
140
+
169
141
  def is_acquired(self) -> bool:
170
142
  """
171
143
  Check if the resource is currently acquired.
172
-
144
+
173
145
  Returns:
174
146
  True if resource is acquired, False otherwise
175
147
  """
176
148
  return self._is_acquired_flag
177
-
149
+
178
150
  def _create_resource(self) -> Any:
179
151
  """
180
152
  Create the resource to be managed.
181
-
153
+
182
154
  This method should be overridden by subclasses to provide
183
155
  specific resource creation logic.
184
-
156
+
185
157
  Returns:
186
158
  The created resource
187
-
159
+
188
160
  Raises:
189
161
  NotImplementedError: If not overridden by subclass
190
162
  """
191
163
  raise NotImplementedError("Subclasses must implement _create_resource")
192
-
164
+
193
165
  def _cleanup_resource(self) -> None:
194
166
  """
195
167
  Clean up the managed resource.
196
-
168
+
197
169
  This method should be overridden by subclasses to provide
198
170
  specific resource cleanup logic.
199
171
  """
200
- if self._resource is not None and hasattr(self._resource, 'close'):
172
+ if self._resource is not None and hasattr(self._resource, "close"):
201
173
  self._resource.close()
202
174
 
203
175
 
204
176
  class FileResourceManager:
205
177
  """
206
178
  Context manager for safe file operations with automatic cleanup.
207
-
179
+
208
180
  This class provides context managers for reading and writing files
209
181
  with proper error handling and resource cleanup.
210
182
  """
211
-
183
+
212
184
  def __init__(
213
185
  self,
214
186
  file_path: str | Path,
@@ -217,11 +189,11 @@ class FileResourceManager:
217
189
  encoding: str | None = DEFAULT_ENCODING,
218
190
  errors: str | None = None,
219
191
  newline: str | None = None,
220
- buffering: int = DEFAULT_BUFFERING
192
+ buffering: int = DEFAULT_BUFFERING,
221
193
  ) -> None:
222
194
  """
223
195
  Initialize FileResourceManager.
224
-
196
+
225
197
  Args:
226
198
  file_path: Path to the file
227
199
  mode: File open mode ('r', 'w', 'a', etc.)
@@ -229,16 +201,13 @@ class FileResourceManager:
229
201
  errors: Error handling for encoding
230
202
  newline: Newline handling
231
203
  buffering: Buffer size
232
-
204
+
233
205
  Raises:
234
206
  SplurgePathValidationError: If file path is invalid
235
207
  SplurgeResourceAcquisitionError: If file cannot be opened
236
208
  """
237
209
  self._file_path = PathValidator.validate_path(
238
- file_path,
239
- must_exist=(mode in ['r', 'rb']),
240
- must_be_file=True,
241
- must_be_readable=(mode in ['r', 'rb'])
210
+ file_path, must_exist=(mode in ["r", "rb"]), must_be_file=True, must_be_readable=(mode in ["r", "rb"])
242
211
  )
243
212
  self.mode = mode
244
213
  self.encoding = encoding
@@ -246,14 +215,14 @@ class FileResourceManager:
246
215
  self.newline = newline
247
216
  self.buffering = buffering
248
217
  self._file_handle: IO[Any] | None = None
249
-
218
+
250
219
  def __enter__(self) -> IO[Any]:
251
220
  """
252
221
  Open the file and return the file handle.
253
-
222
+
254
223
  Returns:
255
224
  File handle
256
-
225
+
257
226
  Raises:
258
227
  SplurgeFileNotFoundError: If file is not found
259
228
  SplurgeFilePermissionError: If permission is denied
@@ -266,19 +235,14 @@ class FileResourceManager:
266
235
  encoding=self.encoding,
267
236
  errors=self.errors,
268
237
  newline=self.newline,
269
- buffering=self.buffering
238
+ buffering=self.buffering,
270
239
  )
271
240
  return self._file_handle
272
-
273
- def __exit__(
274
- self,
275
- exc_type: type | None,
276
- exc_val: Exception | None,
277
- exc_tb: Any | None
278
- ) -> None:
241
+
242
+ def __exit__(self, exc_type: type | None, exc_val: Exception | None, exc_tb: Any | None) -> None:
279
243
  """
280
244
  Close the file handle and cleanup resources.
281
-
245
+
282
246
  Args:
283
247
  exc_type: Exception type if an exception occurred
284
248
  exc_val: Exception value if an exception occurred
@@ -288,13 +252,10 @@ class FileResourceManager:
288
252
  try:
289
253
  self._file_handle.close()
290
254
  except OSError as e:
291
- raise SplurgeResourceReleaseError(
292
- f"Failed to close file: {self.file_path}",
293
- details=str(e)
294
- )
255
+ raise SplurgeResourceReleaseError(f"Failed to close file: {self.file_path}", details=str(e)) from e
295
256
  finally:
296
257
  self._file_handle = None
297
-
258
+
298
259
  @property
299
260
  def file_path(self) -> Path | None:
300
261
  """Get the path of the temporary file."""
@@ -304,20 +265,15 @@ class FileResourceManager:
304
265
  class StreamResourceManager:
305
266
  """
306
267
  Context manager for stream operations.
307
-
268
+
308
269
  This class provides context managers for managing data streams
309
270
  with proper cleanup and error handling.
310
271
  """
311
-
312
- def __init__(
313
- self,
314
- stream: Iterator[Any],
315
- *,
316
- auto_close: bool = True
317
- ) -> None:
272
+
273
+ def __init__(self, stream: Iterator[Any], *, auto_close: bool = True) -> None:
318
274
  """
319
275
  Initialize StreamResourceManager.
320
-
276
+
321
277
  Args:
322
278
  stream: Iterator to manage
323
279
  auto_close: Whether to automatically close the stream
@@ -325,42 +281,34 @@ class StreamResourceManager:
325
281
  self.stream = stream
326
282
  self.auto_close = auto_close
327
283
  self._is_closed = False
328
-
284
+
329
285
  def __enter__(self) -> Iterator[Any]:
330
286
  """
331
287
  Return the stream.
332
-
288
+
333
289
  Returns:
334
290
  Stream iterator
335
291
  """
336
292
  return self.stream
337
-
338
- def __exit__(
339
- self,
340
- exc_type: type | None,
341
- exc_val: Exception | None,
342
- exc_tb: Any | None
343
- ) -> None:
293
+
294
+ def __exit__(self, exc_type: type | None, exc_val: Exception | None, exc_tb: Any | None) -> None:
344
295
  """
345
296
  Clean up the stream.
346
-
297
+
347
298
  Args:
348
299
  exc_type: Exception type if an exception occurred
349
300
  exc_val: Exception value if an exception occurred
350
301
  exc_tb: Exception traceback if an exception occurred
351
302
  """
352
- if self.auto_close and hasattr(self.stream, 'close'):
303
+ if self.auto_close and hasattr(self.stream, "close"):
353
304
  try:
354
305
  self.stream.close()
355
306
  except Exception as e:
356
- raise SplurgeResourceReleaseError(
357
- "Failed to close stream",
358
- details=str(e)
359
- )
360
-
307
+ raise SplurgeResourceReleaseError("Failed to close stream", details=str(e)) from e
308
+
361
309
  # Mark as closed after context manager exits, regardless of close method
362
310
  self._is_closed = True
363
-
311
+
364
312
  @property
365
313
  def is_closed(self) -> bool:
366
314
  """Check if the stream is closed."""
@@ -375,11 +323,11 @@ def safe_file_operation(
375
323
  encoding: str | None = DEFAULT_ENCODING,
376
324
  errors: str | None = None,
377
325
  newline: str | None = None,
378
- buffering: int = DEFAULT_BUFFERING
326
+ buffering: int = DEFAULT_BUFFERING,
379
327
  ) -> Iterator[IO[Any]]:
380
328
  """
381
329
  Context manager for safe file operations.
382
-
330
+
383
331
  Args:
384
332
  file_path: Path to the file
385
333
  mode: File open mode
@@ -387,43 +335,34 @@ def safe_file_operation(
387
335
  errors: Error handling for encoding
388
336
  newline: Newline handling
389
337
  buffering: Buffer size
390
-
338
+
391
339
  Yields:
392
340
  File handle
393
-
341
+
394
342
  Raises:
395
343
  SplurgePathValidationError: If file path is invalid
396
344
  SplurgeResourceAcquisitionError: If file cannot be opened
397
345
  SplurgeResourceReleaseError: If file cannot be closed
398
346
  """
399
347
  manager = FileResourceManager(
400
- file_path,
401
- mode=mode,
402
- encoding=encoding,
403
- errors=errors,
404
- newline=newline,
405
- buffering=buffering
348
+ file_path, mode=mode, encoding=encoding, errors=errors, newline=newline, buffering=buffering
406
349
  )
407
350
  with manager as file_handle:
408
351
  yield file_handle
409
352
 
410
353
 
411
354
  @contextmanager
412
- def safe_stream_operation(
413
- stream: Iterator[Any],
414
- *,
415
- auto_close: bool = True
416
- ) -> Iterator[Iterator[Any]]:
355
+ def safe_stream_operation(stream: Iterator[Any], *, auto_close: bool = True) -> Iterator[Iterator[Any]]:
417
356
  """
418
357
  Context manager for safe stream operations.
419
-
358
+
420
359
  Args:
421
360
  stream: Iterator to manage
422
361
  auto_close: Whether to automatically close the stream
423
-
362
+
424
363
  Yields:
425
364
  Stream iterator
426
-
365
+
427
366
  Raises:
428
367
  SplurgeResourceReleaseError: If stream cannot be closed
429
368
  """
@@ -11,6 +11,7 @@ Please preserve this header and all related material when sharing!
11
11
  This module is licensed under the MIT License.
12
12
  """
13
13
 
14
+ # Local imports
14
15
  from splurge_dsv.exceptions import SplurgeParameterError
15
16
 
16
17
 
@@ -27,12 +28,7 @@ class StringTokenizer:
27
28
  DEFAULT_STRIP = True
28
29
 
29
30
  @staticmethod
30
- def parse(
31
- content: str | None,
32
- *,
33
- delimiter: str,
34
- strip: bool = DEFAULT_STRIP
35
- ) -> list[str]:
31
+ def parse(content: str | None, *, delimiter: str, strip: bool = DEFAULT_STRIP) -> list[str]:
36
32
  """
37
33
  Split a string into tokens based on a delimiter.
38
34
 
@@ -68,13 +64,7 @@ class StringTokenizer:
68
64
  return result
69
65
 
70
66
  @classmethod
71
- def parses(
72
- cls,
73
- content: list[str],
74
- *,
75
- delimiter: str,
76
- strip: bool = DEFAULT_STRIP
77
- ) -> list[list[str]]:
67
+ def parses(cls, content: list[str], *, delimiter: str, strip: bool = DEFAULT_STRIP) -> list[list[str]]:
78
68
  """
79
69
  Process multiple strings into lists of tokens.
80
70
 
@@ -99,12 +89,7 @@ class StringTokenizer:
99
89
  return [cls.parse(text, delimiter=delimiter, strip=strip) for text in content]
100
90
 
101
91
  @staticmethod
102
- def remove_bookends(
103
- content: str,
104
- *,
105
- bookend: str,
106
- strip: bool = DEFAULT_STRIP
107
- ) -> str:
92
+ def remove_bookends(content: str, *, bookend: str, strip: bool = DEFAULT_STRIP) -> str:
108
93
  """
109
94
  Remove matching characters from both ends of a string.
110
95
 
@@ -127,10 +112,6 @@ class StringTokenizer:
127
112
  raise SplurgeParameterError("bookend cannot be empty or None")
128
113
 
129
114
  value: str = content.strip() if strip else content
130
- if (
131
- value.startswith(bookend)
132
- and value.endswith(bookend)
133
- and len(value) > 2 * len(bookend) - 1
134
- ):
115
+ if value.startswith(bookend) and value.endswith(bookend) and len(value) > 2 * len(bookend) - 1:
135
116
  return value[len(bookend) : -len(bookend)]
136
117
  return value