splurge-dsv 2025.1.5__py3-none-any.whl → 2025.2.1__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,268 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: splurge-dsv
3
- Version: 2025.1.5
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
- [![PyPI version](https://badge.fury.io/py/splurge-dsv.svg)](https://pypi.org/project/splurge-dsv/)
33
- [![Python versions](https://img.shields.io/pypi/pyversions/splurge-dsv.svg)](https://pypi.org/project/splurge-dsv/)
34
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
35
- [![Coverage](https://img.shields.io/badge/coverage-96%25-brightgreen.svg)](https://github.com/jim-schilling/splurge-dsv)
36
-
37
- A robust Python library for parsing and processing delimited-separated value (DSV) files with advanced features for data validation, streaming, and error handling.
38
-
39
- ## Features
40
-
41
- ### 🔧 Core Functionality
42
- - **Multi-format DSV Support**: Parse CSV, TSV, pipe-delimited, semicolon-delimited, and custom delimiter files
43
- - **Flexible Parsing Options**: Configurable whitespace handling, bookend removal, and encoding support
44
- - **Memory-Efficient Streaming**: Process large files without loading entire content into memory
45
- - **Header/Footer Skipping**: Skip specified numbers of rows from start or end of files
46
- - **Unicode Support**: Full Unicode character and delimiter support
47
-
48
- ### 🛡️ Security & Validation
49
- - **Path Validation**: Comprehensive file path security validation with traversal attack prevention
50
- - **File Permission Checks**: Automatic file accessibility and permission validation
51
- - **Encoding Validation**: Robust encoding error detection and handling
52
- - **Resource Management**: Automatic file handle cleanup and resource management
53
-
54
- ### 📊 Advanced Processing
55
- - **Chunked Processing**: Configurable chunk sizes for streaming large datasets
56
- - **Mixed Content Handling**: Support for quoted and unquoted values in the same file
57
- - **Line Ending Flexibility**: Automatic handling of different line ending formats
58
- - **Error Recovery**: Graceful error handling with detailed error messages
59
-
60
- ### 🧪 Testing & Quality
61
- - **Comprehensive Test Suite**: 250+ tests with 85%+ coverage gate
62
- - **Cross-Platform Support**: Tested on Windows, and should pass on Linux and macOS
63
- - **Type Safety**: Full type annotations and validation
64
- - **Documentation**: Complete API documentation with examples
65
-
66
- ## Installation
67
-
68
- ```bash
69
- pip install splurge-dsv
70
- ```
71
-
72
- ## Quick Start
73
-
74
- ### Basic CSV Parsing
75
-
76
- ```python
77
- from splurge_dsv import DsvHelper
78
-
79
- # Parse a simple CSV string
80
- data = DsvHelper.parse("a,b,c", delimiter=",")
81
- print(data) # ['a', 'b', 'c']
82
-
83
- # Parse a CSV file
84
- rows = DsvHelper.parse_file("data.csv", delimiter=",")
85
- for row in rows:
86
- print(row) # ['col1', 'col2', 'col3']
87
- ```
88
-
89
- ### Streaming Large Files
90
-
91
- ```python
92
- from splurge_dsv import DsvHelper
93
-
94
- # Stream a large CSV file in chunks
95
- for chunk in DsvHelper.parse_stream("large_file.csv", delimiter=",", chunk_size=1000):
96
- for row in chunk:
97
- process_row(row)
98
- ```
99
-
100
- ### Advanced Parsing Options
101
-
102
- ```python
103
- from splurge_dsv import DsvHelper
104
-
105
- # Parse with custom options
106
- data = DsvHelper.parse(
107
- '"a","b","c"',
108
- delimiter=",",
109
- bookend='"',
110
- strip=True,
111
- bookend_strip=True
112
- )
113
- print(data) # ['a', 'b', 'c']
114
-
115
- # Skip header and footer rows
116
- rows = DsvHelper.parse_file(
117
- "data.csv",
118
- delimiter=",",
119
- skip_header_rows=1,
120
- skip_footer_rows=2
121
- )
122
- ```
123
-
124
- ### Text File Operations
125
-
126
- ```python
127
- from splurge_dsv import TextFileHelper
128
-
129
- # Count lines in a file
130
- line_count = TextFileHelper.line_count("data.txt")
131
-
132
- # Preview first N lines
133
- preview = TextFileHelper.preview("data.txt", max_lines=10)
134
-
135
- # Read entire file with options
136
- lines = TextFileHelper.read(
137
- "data.txt",
138
- strip=True,
139
- skip_header_rows=1,
140
- skip_footer_rows=1
141
- )
142
-
143
- # Stream file content
144
- for chunk in TextFileHelper.read_as_stream("large_file.txt", chunk_size=500):
145
- process_chunk(chunk)
146
- ```
147
-
148
- ### Path Validation
149
-
150
- ```python
151
- from splurge_dsv import PathValidator
152
-
153
- # Validate a file path
154
- valid_path = PathValidator.validate_path(
155
- "data.csv",
156
- must_exist=True,
157
- must_be_file=True,
158
- must_be_readable=True
159
- )
160
-
161
- # Check if path is safe
162
- is_safe = PathValidator.is_safe_path("user_input_path.txt")
163
- ```
164
-
165
- ## API Reference
166
-
167
- ### DsvHelper
168
-
169
- Main class for DSV parsing operations.
170
-
171
- #### Methods
172
-
173
- - `parse(content, delimiter, strip=True, bookend=None, bookend_strip=True)` - Parse a single string
174
- - `parses(content_list, delimiter, strip=True, bookend=None, bookend_strip=True)` - Parse multiple strings
175
- - `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
176
- - `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
177
-
178
- ### TextFileHelper
179
-
180
- Utility class for text file operations.
181
-
182
- #### Methods
183
-
184
- - `line_count(file_path, encoding='utf-8')` - Count lines in a file
185
- - `preview(file_path, max_lines=100, strip=True, encoding='utf-8', skip_header_rows=0)` - Preview file content
186
- - `read(file_path, strip=True, encoding='utf-8', skip_header_rows=0, skip_footer_rows=0)` - Read entire file
187
- - `read_as_stream(file_path, strip=True, encoding='utf-8', skip_header_rows=0, skip_footer_rows=0, chunk_size=500)` - Stream read file
188
-
189
- ### PathValidator
190
-
191
- Security-focused path validation utilities.
192
-
193
- #### Methods
194
-
195
- - `validate_path(file_path, must_exist=False, must_be_file=False, must_be_readable=False, allow_relative=False, base_directory=None)` - Validate file path
196
- - `is_safe_path(file_path)` - Check if path is safe
197
- - `sanitize_filename(filename, default_name='file')` - Sanitize filename
198
-
199
- ### ResourceManager
200
-
201
- Context managers for safe resource handling.
202
-
203
- #### Classes
204
-
205
- - `FileResourceManager` - Context manager for file operations
206
- - `StreamResourceManager` - Context manager for stream operations
207
-
208
- #### Functions
209
-
210
- - `safe_file_operation(file_path, mode='r', encoding='utf-8', ...)` - Safe file operation context manager
211
- - `safe_stream_operation(stream, auto_close=True)` - Safe stream operation context manager
212
-
213
- ## Error Handling
214
-
215
- The library provides comprehensive error handling with custom exception classes:
216
-
217
- - `SplurgeParameterError` - Invalid parameter values
218
- - `SplurgeFileNotFoundError` - File not found
219
- - `SplurgeFilePermissionError` - File permission issues
220
- - `SplurgeFileEncodingError` - File encoding problems
221
- - `SplurgePathValidationError` - Path validation failures
222
- - `SplurgeResourceAcquisitionError` - Resource acquisition failures
223
- - `SplurgeResourceReleaseError` - Resource cleanup failures
224
-
225
- ## Development
226
-
227
- ### Running Tests
228
-
229
- ```bash
230
- # Run all tests
231
- pytest tests/ -v
232
-
233
- # Run with coverage
234
- pytest tests/ --cov=splurge_dsv --cov-report=html
235
-
236
- # Run specific test file
237
- pytest tests/test_dsv_helper.py -v
238
- ```
239
-
240
- ### Code Quality
241
-
242
- The project follows strict coding standards:
243
- - PEP 8 compliance
244
- - Type annotations for all functions
245
- - Google-style docstrings
246
- - 85%+ coverage gate enforced via CI
247
- - Comprehensive error handling
248
-
249
- ## Changelog
250
-
251
- See the [CHANGELOG](CHANGELOG.md) for full release notes.
252
-
253
- ## License
254
-
255
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
256
-
257
- ## More Documentation
258
-
259
- - Detailed docs: [docs/README-details.md](docs/README-details.md)
260
- - E2E testing coverage: [docs/e2e_testing_coverage.md](docs/e2e_testing_coverage.md)
261
-
262
- ## Contributing
263
-
264
- 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.
265
-
266
- ## Support
267
-
268
- For support, please open an issue on the GitHub repository or contact the maintainers.
@@ -1,14 +0,0 @@
1
- splurge_dsv/__init__.py,sha256=cqq1MreFbu8ib04OyzkydhLc6ZpJSnc3jyMqoEouABo,2415
2
- splurge_dsv/__main__.py,sha256=CX8zVMaK6vaaCaC396FyXA1E_-jqKz4zh3CVW8mGKac,359
3
- splurge_dsv/cli.py,sha256=QLFdb22MSpQF3iXwxdEhETYWTup9y2pGfT6Vt3TzKuA,7612
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.5.dist-info/licenses/LICENSE,sha256=fPgtg-tIFHinQvJH0arRfv50AuxikD5eHw6rrPy2A5w,1091
11
- splurge_dsv-2025.1.5.dist-info/METADATA,sha256=-dWt2wSfflEcg7KWeftltEFaNEve5Mdwxx3MZ9SRFAQ,9023
12
- splurge_dsv-2025.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- splurge_dsv-2025.1.5.dist-info/top_level.txt,sha256=D6Si3FTfpRYqH7kzM7tSQAyaKbbraO6UPLpcqcY4XXM,12
14
- splurge_dsv-2025.1.5.dist-info/RECORD,,