splurge-dsv 2025.1.4__py3-none-any.whl → 2025.2.0__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.
@@ -1,371 +0,0 @@
1
- """
2
- Resource management utilities with context managers.
3
-
4
- This module provides context managers and resource management utilities
5
- for safe handling of file operations, streams, and other resources.
6
-
7
- Copyright (c) 2025 Jim Schilling
8
-
9
- Please preserve this header and all related material when sharing!
10
-
11
- This module is licensed under the MIT License.
12
- """
13
-
14
- # Standard library imports
15
- from collections.abc import Iterator
16
- from contextlib import contextmanager
17
- from pathlib import Path
18
- from typing import IO, Any
19
-
20
- # Local imports
21
- from splurge_dsv.exceptions import (
22
- SplurgeFileEncodingError,
23
- SplurgeFileNotFoundError,
24
- SplurgeFilePermissionError,
25
- SplurgeResourceAcquisitionError,
26
- SplurgeResourceReleaseError,
27
- )
28
- from splurge_dsv.path_validator import PathValidator
29
-
30
- # Module-level constants for resource management
31
- DEFAULT_BUFFERING = -1 # Default buffering for file operations
32
- DEFAULT_ENCODING = "utf-8" # Default text encoding
33
- DEFAULT_MODE = "r" # Default file mode for reading
34
-
35
-
36
- def _safe_open_file(
37
- file_path: Path,
38
- *,
39
- mode: str,
40
- encoding: str | None = None,
41
- errors: str | None = None,
42
- newline: str | None = None,
43
- buffering: int = DEFAULT_BUFFERING,
44
- ) -> IO[Any]:
45
- """
46
- Safely open a file with proper error handling.
47
-
48
- This function provides centralized file opening with consistent error handling
49
- that converts standard file operation exceptions to custom exceptions.
50
-
51
- Args:
52
- file_path: Path to the file
53
- mode: File open mode
54
- encoding: Text encoding (for text mode)
55
- errors: Error handling for encoding
56
- newline: Newline handling
57
- buffering: Buffer size
58
-
59
- Returns:
60
- File handle
61
-
62
- Raises:
63
- SplurgeFileNotFoundError: If file is not found
64
- SplurgeFilePermissionError: If permission is denied
65
- SplurgeFileEncodingError: If encoding error occurs
66
- SplurgeResourceAcquisitionError: If other file operation fails
67
- """
68
- try:
69
- if "b" in mode:
70
- # Binary mode
71
- return open(file_path, mode=mode, buffering=buffering)
72
- else:
73
- # Text mode
74
- return open(file_path, mode=mode, encoding=encoding, errors=errors, newline=newline, buffering=buffering)
75
- except FileNotFoundError as e:
76
- raise SplurgeFileNotFoundError(f"File not found: {file_path}", details=str(e)) from e
77
- except PermissionError as e:
78
- raise SplurgeFilePermissionError(f"Permission denied: {file_path}", details=str(e)) from e
79
- except UnicodeDecodeError as e:
80
- raise SplurgeFileEncodingError(f"Encoding error reading file: {file_path}", details=str(e)) from e
81
- except OSError as e:
82
- raise SplurgeResourceAcquisitionError(f"Failed to open file: {file_path}", details=str(e)) from e
83
-
84
-
85
- class ResourceManager:
86
- """
87
- Generic resource manager that implements the ResourceManagerProtocol.
88
-
89
- This class provides a base implementation for resource management
90
- with acquire/release semantics.
91
- """
92
-
93
- def __init__(self) -> None:
94
- """Initialize the resource manager."""
95
- self._resource: Any | None = None
96
- self._is_acquired_flag: bool = False
97
-
98
- def acquire(self) -> Any:
99
- """
100
- Acquire the managed resource.
101
-
102
- Returns:
103
- The acquired resource
104
-
105
- Raises:
106
- NotImplementedError: If _create_resource is not implemented by subclass
107
- SplurgeResourceAcquisitionError: If resource cannot be acquired
108
- """
109
- if self._is_acquired_flag:
110
- raise SplurgeResourceAcquisitionError(
111
- "Resource is already acquired", details="Cannot acquire resource that is already in use"
112
- )
113
-
114
- try:
115
- self._resource = self._create_resource()
116
- self._is_acquired_flag = True
117
- return self._resource
118
- except NotImplementedError:
119
- # Re-raise NotImplementedError without wrapping it
120
- raise
121
- except Exception as e:
122
- raise SplurgeResourceAcquisitionError("Failed to acquire resource", details=str(e)) from e
123
-
124
- def release(self) -> None:
125
- """
126
- Release the managed resource.
127
-
128
- Raises:
129
- SplurgeResourceReleaseError: If resource cannot be released
130
- """
131
- if not self._is_acquired_flag:
132
- return # Nothing to release
133
-
134
- try:
135
- self._cleanup_resource()
136
- self._resource = None
137
- self._is_acquired_flag = False
138
- except Exception as e:
139
- raise SplurgeResourceReleaseError("Failed to release resource", details=str(e)) from e
140
-
141
- def is_acquired(self) -> bool:
142
- """
143
- Check if the resource is currently acquired.
144
-
145
- Returns:
146
- True if resource is acquired, False otherwise
147
- """
148
- return self._is_acquired_flag
149
-
150
- def _create_resource(self) -> Any:
151
- """
152
- Create the resource to be managed.
153
-
154
- This method should be overridden by subclasses to provide
155
- specific resource creation logic.
156
-
157
- Returns:
158
- The created resource
159
-
160
- Raises:
161
- NotImplementedError: If not overridden by subclass
162
- """
163
- raise NotImplementedError("Subclasses must implement _create_resource")
164
-
165
- def _cleanup_resource(self) -> None:
166
- """
167
- Clean up the managed resource.
168
-
169
- This method should be overridden by subclasses to provide
170
- specific resource cleanup logic.
171
- """
172
- if self._resource is not None and hasattr(self._resource, "close"):
173
- self._resource.close()
174
-
175
-
176
- class FileResourceManager:
177
- """
178
- Context manager for safe file operations with automatic cleanup.
179
-
180
- This class provides context managers for reading and writing files
181
- with proper error handling and resource cleanup.
182
- """
183
-
184
- def __init__(
185
- self,
186
- file_path: str | Path,
187
- *,
188
- mode: str = DEFAULT_MODE,
189
- encoding: str | None = DEFAULT_ENCODING,
190
- errors: str | None = None,
191
- newline: str | None = None,
192
- buffering: int = DEFAULT_BUFFERING,
193
- ) -> None:
194
- """
195
- Initialize FileResourceManager.
196
-
197
- Args:
198
- file_path: Path to the file
199
- mode: File open mode ('r', 'w', 'a', etc.)
200
- encoding: Text encoding (for text mode)
201
- errors: Error handling for encoding
202
- newline: Newline handling
203
- buffering: Buffer size
204
-
205
- Raises:
206
- SplurgePathValidationError: If file path is invalid
207
- SplurgeResourceAcquisitionError: If file cannot be opened
208
- """
209
- self._file_path = PathValidator.validate_path(
210
- file_path, must_exist=(mode in ["r", "rb"]), must_be_file=True, must_be_readable=(mode in ["r", "rb"])
211
- )
212
- self.mode = mode
213
- self.encoding = encoding
214
- self.errors = errors
215
- self.newline = newline
216
- self.buffering = buffering
217
- self._file_handle: IO[Any] | None = None
218
-
219
- def __enter__(self) -> IO[Any]:
220
- """
221
- Open the file and return the file handle.
222
-
223
- Returns:
224
- File handle
225
-
226
- Raises:
227
- SplurgeFileNotFoundError: If file is not found
228
- SplurgeFilePermissionError: If permission is denied
229
- SplurgeFileEncodingError: If encoding error occurs
230
- SplurgeResourceAcquisitionError: If other file operation fails
231
- """
232
- self._file_handle = _safe_open_file(
233
- self.file_path,
234
- mode=self.mode,
235
- encoding=self.encoding,
236
- errors=self.errors,
237
- newline=self.newline,
238
- buffering=self.buffering,
239
- )
240
- return self._file_handle
241
-
242
- def __exit__(self, exc_type: type | None, exc_val: Exception | None, exc_tb: Any | None) -> None:
243
- """
244
- Close the file handle and cleanup resources.
245
-
246
- Args:
247
- exc_type: Exception type if an exception occurred
248
- exc_val: Exception value if an exception occurred
249
- exc_tb: Exception traceback if an exception occurred
250
- """
251
- if self._file_handle is not None:
252
- try:
253
- self._file_handle.close()
254
- except OSError as e:
255
- raise SplurgeResourceReleaseError(f"Failed to close file: {self.file_path}", details=str(e)) from e
256
- finally:
257
- self._file_handle = None
258
-
259
- @property
260
- def file_path(self) -> Path | None:
261
- """Get the path of the temporary file."""
262
- return self._file_path
263
-
264
-
265
- class StreamResourceManager:
266
- """
267
- Context manager for stream operations.
268
-
269
- This class provides context managers for managing data streams
270
- with proper cleanup and error handling.
271
- """
272
-
273
- def __init__(self, stream: Iterator[Any], *, auto_close: bool = True) -> None:
274
- """
275
- Initialize StreamResourceManager.
276
-
277
- Args:
278
- stream: Iterator to manage
279
- auto_close: Whether to automatically close the stream
280
- """
281
- self.stream = stream
282
- self.auto_close = auto_close
283
- self._is_closed = False
284
-
285
- def __enter__(self) -> Iterator[Any]:
286
- """
287
- Return the stream.
288
-
289
- Returns:
290
- Stream iterator
291
- """
292
- return self.stream
293
-
294
- def __exit__(self, exc_type: type | None, exc_val: Exception | None, exc_tb: Any | None) -> None:
295
- """
296
- Clean up the stream.
297
-
298
- Args:
299
- exc_type: Exception type if an exception occurred
300
- exc_val: Exception value if an exception occurred
301
- exc_tb: Exception traceback if an exception occurred
302
- """
303
- if self.auto_close and hasattr(self.stream, "close"):
304
- try:
305
- self.stream.close()
306
- except Exception as e:
307
- raise SplurgeResourceReleaseError("Failed to close stream", details=str(e)) from e
308
-
309
- # Mark as closed after context manager exits, regardless of close method
310
- self._is_closed = True
311
-
312
- @property
313
- def is_closed(self) -> bool:
314
- """Check if the stream is closed."""
315
- return self._is_closed
316
-
317
-
318
- @contextmanager
319
- def safe_file_operation(
320
- file_path: str | Path,
321
- *,
322
- mode: str = DEFAULT_MODE,
323
- encoding: str | None = DEFAULT_ENCODING,
324
- errors: str | None = None,
325
- newline: str | None = None,
326
- buffering: int = DEFAULT_BUFFERING,
327
- ) -> Iterator[IO[Any]]:
328
- """
329
- Context manager for safe file operations.
330
-
331
- Args:
332
- file_path: Path to the file
333
- mode: File open mode
334
- encoding: Text encoding (for text mode)
335
- errors: Error handling for encoding
336
- newline: Newline handling
337
- buffering: Buffer size
338
-
339
- Yields:
340
- File handle
341
-
342
- Raises:
343
- SplurgePathValidationError: If file path is invalid
344
- SplurgeResourceAcquisitionError: If file cannot be opened
345
- SplurgeResourceReleaseError: If file cannot be closed
346
- """
347
- manager = FileResourceManager(
348
- file_path, mode=mode, encoding=encoding, errors=errors, newline=newline, buffering=buffering
349
- )
350
- with manager as file_handle:
351
- yield file_handle
352
-
353
-
354
- @contextmanager
355
- def safe_stream_operation(stream: Iterator[Any], *, auto_close: bool = True) -> Iterator[Iterator[Any]]:
356
- """
357
- Context manager for safe stream operations.
358
-
359
- Args:
360
- stream: Iterator to manage
361
- auto_close: Whether to automatically close the stream
362
-
363
- Yields:
364
- Stream iterator
365
-
366
- Raises:
367
- SplurgeResourceReleaseError: If stream cannot be closed
368
- """
369
- manager = StreamResourceManager(stream, auto_close=auto_close)
370
- with manager as stream_handle:
371
- yield stream_handle
@@ -1,263 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: splurge-dsv
3
- Version: 2025.1.4
4
- Summary: A utility library for working with DSV (Delimited String Values) files
5
- Author: Jim Schilling
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/jim-schilling/splurge-dsv
8
- Project-URL: Repository, https://github.com/jim-schilling/splurge-dsv
9
- Project-URL: Documentation, https://github.com/jim-schilling/splurge-dsv#readme
10
- Project-URL: Bug Tracker, https://github.com/jim-schilling/splurge-dsv/issues
11
- Keywords: dsv,csv,tsv,delimited,parsing,file-processing
12
- Classifier: Development Status :: 3 - Alpha
13
- Classifier: Intended Audience :: Developers
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Programming Language :: Python :: 3.13
19
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
- Classifier: Topic :: Text Processing :: Filters
21
- Requires-Python: >=3.10
22
- Description-Content-Type: text/markdown
23
- License-File: LICENSE
24
- Provides-Extra: dev
25
- Requires-Dist: pytest>=7.0.0; extra == "dev"
26
- Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
27
- Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
28
- Dynamic: license-file
29
-
30
- # splurge-dsv
31
-
32
- A robust Python library for parsing and processing delimited-separated value (DSV) files with advanced features for data validation, streaming, and error handling.
33
-
34
- ## Features
35
-
36
- ### 🔧 Core Functionality
37
- - **Multi-format DSV Support**: Parse CSV, TSV, pipe-delimited, semicolon-delimited, and custom delimiter files
38
- - **Flexible Parsing Options**: Configurable whitespace handling, bookend removal, and encoding support
39
- - **Memory-Efficient Streaming**: Process large files without loading entire content into memory
40
- - **Header/Footer Skipping**: Skip specified numbers of rows from start or end of files
41
- - **Unicode Support**: Full Unicode character and delimiter support
42
-
43
- ### 🛡️ Security & Validation
44
- - **Path Validation**: Comprehensive file path security validation with traversal attack prevention
45
- - **File Permission Checks**: Automatic file accessibility and permission validation
46
- - **Encoding Validation**: Robust encoding error detection and handling
47
- - **Resource Management**: Automatic file handle cleanup and resource management
48
-
49
- ### 📊 Advanced Processing
50
- - **Chunked Processing**: Configurable chunk sizes for streaming large datasets
51
- - **Mixed Content Handling**: Support for quoted and unquoted values in the same file
52
- - **Line Ending Flexibility**: Automatic handling of different line ending formats
53
- - **Error Recovery**: Graceful error handling with detailed error messages
54
-
55
- ### 🧪 Testing & Quality
56
- - **Comprehensive Test Suite**: 250+ tests with 85%+ coverage gate
57
- - **Cross-Platform Support**: Tested on Windows, and should pass on Linux and macOS
58
- - **Type Safety**: Full type annotations and validation
59
- - **Documentation**: Complete API documentation with examples
60
-
61
- ## Installation
62
-
63
- ```bash
64
- pip install splurge-dsv
65
- ```
66
-
67
- ## Quick Start
68
-
69
- ### Basic CSV Parsing
70
-
71
- ```python
72
- from splurge_dsv import DsvHelper
73
-
74
- # Parse a simple CSV string
75
- data = DsvHelper.parse("a,b,c", delimiter=",")
76
- print(data) # ['a', 'b', 'c']
77
-
78
- # Parse a CSV file
79
- rows = DsvHelper.parse_file("data.csv", delimiter=",")
80
- for row in rows:
81
- print(row) # ['col1', 'col2', 'col3']
82
- ```
83
-
84
- ### Streaming Large Files
85
-
86
- ```python
87
- from splurge_dsv import DsvHelper
88
-
89
- # Stream a large CSV file in chunks
90
- for chunk in DsvHelper.parse_stream("large_file.csv", delimiter=",", chunk_size=1000):
91
- for row in chunk:
92
- process_row(row)
93
- ```
94
-
95
- ### Advanced Parsing Options
96
-
97
- ```python
98
- from splurge_dsv import DsvHelper
99
-
100
- # Parse with custom options
101
- data = DsvHelper.parse(
102
- '"a","b","c"',
103
- delimiter=",",
104
- bookend='"',
105
- strip=True,
106
- bookend_strip=True
107
- )
108
- print(data) # ['a', 'b', 'c']
109
-
110
- # Skip header and footer rows
111
- rows = DsvHelper.parse_file(
112
- "data.csv",
113
- delimiter=",",
114
- skip_header_rows=1,
115
- skip_footer_rows=2
116
- )
117
- ```
118
-
119
- ### Text File Operations
120
-
121
- ```python
122
- from splurge_dsv import TextFileHelper
123
-
124
- # Count lines in a file
125
- line_count = TextFileHelper.line_count("data.txt")
126
-
127
- # Preview first N lines
128
- preview = TextFileHelper.preview("data.txt", max_lines=10)
129
-
130
- # Read entire file with options
131
- lines = TextFileHelper.read(
132
- "data.txt",
133
- strip=True,
134
- skip_header_rows=1,
135
- skip_footer_rows=1
136
- )
137
-
138
- # Stream file content
139
- for chunk in TextFileHelper.read_as_stream("large_file.txt", chunk_size=500):
140
- process_chunk(chunk)
141
- ```
142
-
143
- ### Path Validation
144
-
145
- ```python
146
- from splurge_dsv import PathValidator
147
-
148
- # Validate a file path
149
- valid_path = PathValidator.validate_path(
150
- "data.csv",
151
- must_exist=True,
152
- must_be_file=True,
153
- must_be_readable=True
154
- )
155
-
156
- # Check if path is safe
157
- is_safe = PathValidator.is_safe_path("user_input_path.txt")
158
- ```
159
-
160
- ## API Reference
161
-
162
- ### DsvHelper
163
-
164
- Main class for DSV parsing operations.
165
-
166
- #### Methods
167
-
168
- - `parse(content, delimiter, strip=True, bookend=None, bookend_strip=True)` - Parse a single string
169
- - `parses(content_list, delimiter, strip=True, bookend=None, bookend_strip=True)` - Parse multiple strings
170
- - `parse_file(file_path, delimiter, strip=True, bookend=None, bookend_strip=True, skip_header_rows=0, skip_footer_rows=0, encoding='utf-8')` - Parse a file
171
- - `parse_stream(file_path, delimiter, strip=True, bookend=None, bookend_strip=True, skip_header_rows=0, skip_footer_rows=0, encoding='utf-8', chunk_size=500)` - Stream parse a file
172
-
173
- ### TextFileHelper
174
-
175
- Utility class for text file operations.
176
-
177
- #### Methods
178
-
179
- - `line_count(file_path, encoding='utf-8')` - Count lines in a file
180
- - `preview(file_path, max_lines=100, strip=True, encoding='utf-8', skip_header_rows=0)` - Preview file content
181
- - `read(file_path, strip=True, encoding='utf-8', skip_header_rows=0, skip_footer_rows=0)` - Read entire file
182
- - `read_as_stream(file_path, strip=True, encoding='utf-8', skip_header_rows=0, skip_footer_rows=0, chunk_size=500)` - Stream read file
183
-
184
- ### PathValidator
185
-
186
- Security-focused path validation utilities.
187
-
188
- #### Methods
189
-
190
- - `validate_path(file_path, must_exist=False, must_be_file=False, must_be_readable=False, allow_relative=False, base_directory=None)` - Validate file path
191
- - `is_safe_path(file_path)` - Check if path is safe
192
- - `sanitize_filename(filename, default_name='file')` - Sanitize filename
193
-
194
- ### ResourceManager
195
-
196
- Context managers for safe resource handling.
197
-
198
- #### Classes
199
-
200
- - `FileResourceManager` - Context manager for file operations
201
- - `StreamResourceManager` - Context manager for stream operations
202
-
203
- #### Functions
204
-
205
- - `safe_file_operation(file_path, mode='r', encoding='utf-8', ...)` - Safe file operation context manager
206
- - `safe_stream_operation(stream, auto_close=True)` - Safe stream operation context manager
207
-
208
- ## Error Handling
209
-
210
- The library provides comprehensive error handling with custom exception classes:
211
-
212
- - `SplurgeParameterError` - Invalid parameter values
213
- - `SplurgeFileNotFoundError` - File not found
214
- - `SplurgeFilePermissionError` - File permission issues
215
- - `SplurgeFileEncodingError` - File encoding problems
216
- - `SplurgePathValidationError` - Path validation failures
217
- - `SplurgeResourceAcquisitionError` - Resource acquisition failures
218
- - `SplurgeResourceReleaseError` - Resource cleanup failures
219
-
220
- ## Development
221
-
222
- ### Running Tests
223
-
224
- ```bash
225
- # Run all tests
226
- pytest tests/ -v
227
-
228
- # Run with coverage
229
- pytest tests/ --cov=splurge_dsv --cov-report=html
230
-
231
- # Run specific test file
232
- pytest tests/test_dsv_helper.py -v
233
- ```
234
-
235
- ### Code Quality
236
-
237
- The project follows strict coding standards:
238
- - PEP 8 compliance
239
- - Type annotations for all functions
240
- - Google-style docstrings
241
- - 85%+ coverage gate enforced via CI
242
- - Comprehensive error handling
243
-
244
- ## Changelog
245
-
246
- See the [CHANGELOG](CHANGELOG.md) for full release notes.
247
-
248
- ## License
249
-
250
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
251
-
252
- ## More Documentation
253
-
254
- - Detailed docs: [docs/README-details.md](docs/README-details.md)
255
- - E2E testing coverage: [docs/e2e_testing_coverage.md](docs/e2e_testing_coverage.md)
256
-
257
- ## Contributing
258
-
259
- Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
260
-
261
- ## Support
262
-
263
- For support, please open an issue on the GitHub repository or contact the maintainers.
@@ -1,14 +0,0 @@
1
- splurge_dsv/__init__.py,sha256=I_BAM3zHWRZLyY5ndCweBCg1w_cbBDZhdKlxUp6Sx8Q,2415
2
- splurge_dsv/__main__.py,sha256=CX8zVMaK6vaaCaC396FyXA1E_-jqKz4zh3CVW8mGKac,359
3
- splurge_dsv/cli.py,sha256=hcoF2OC3gHrN4SegSUFQeGxHuWWIBAJn-fTNW19KnyA,7261
4
- splurge_dsv/dsv_helper.py,sha256=VRq2ejx6y-JBtFKJdAaK-GD4V0eoxZZfmoX0I3CSbDI,9428
5
- splurge_dsv/exceptions.py,sha256=cu9Jd2pGhy7GBbbngH6zs0lfZzLp_OvGwbnsG5khp80,3035
6
- splurge_dsv/path_validator.py,sha256=RuRFjtHWE1Z5-DlSBBZMoHJegQEFYEQ0HJgN7ndre2k,9969
7
- splurge_dsv/resource_manager.py,sha256=g4igv1hhJdPtw4A1P-WdaNKcYMfEGCtZ4xaU4ynVbKw,12045
8
- splurge_dsv/string_tokenizer.py,sha256=wBKWdi68rreTqf3RF-8Oxh1nz6QdnbOyWflw2x8pGWY,4022
9
- splurge_dsv/text_file_helper.py,sha256=vdhEv9uK0hsNXvTO5SoWwFsI9fPkhdIUXuaJs80DVDA,13573
10
- splurge_dsv-2025.1.4.dist-info/licenses/LICENSE,sha256=fPgtg-tIFHinQvJH0arRfv50AuxikD5eHw6rrPy2A5w,1091
11
- splurge_dsv-2025.1.4.dist-info/METADATA,sha256=sxdtsP6DfbVRkdVx6LcgcH23DEguS01gniAzN9ELlZM,8573
12
- splurge_dsv-2025.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- splurge_dsv-2025.1.4.dist-info/top_level.txt,sha256=D6Si3FTfpRYqH7kzM7tSQAyaKbbraO6UPLpcqcY4XXM,12
14
- splurge_dsv-2025.1.4.dist-info/RECORD,,