esuls 0.1.17__tar.gz → 0.1.18__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.
- {esuls-0.1.17/src/esuls.egg-info → esuls-0.1.18}/PKG-INFO +1 -1
- {esuls-0.1.17 → esuls-0.1.18}/pyproject.toml +1 -1
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls/db_cli.py +12 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls/request_cli.py +22 -1
- {esuls-0.1.17 → esuls-0.1.18/src/esuls.egg-info}/PKG-INFO +1 -1
- {esuls-0.1.17 → esuls-0.1.18}/LICENSE +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/README.md +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/setup.cfg +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls/__init__.py +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls/download_icon.py +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls/tests/test_db_concurrent.py +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls/tests/test_db_fixes.py +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls/utils.py +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls.egg-info/SOURCES.txt +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls.egg-info/dependency_links.txt +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls.egg-info/requires.txt +0 -0
- {esuls-0.1.17 → esuls-0.1.18}/src/esuls.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "esuls"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.18"
|
|
8
8
|
description = "Utility library for async database operations, HTTP requests, and parallel execution"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14"
|
|
@@ -46,6 +46,8 @@ class AsyncDB(Generic[SchemaType]):
|
|
|
46
46
|
_schema_init_lock: asyncio.Lock = None
|
|
47
47
|
# Threading lock to guard class-level dict mutations
|
|
48
48
|
_db_locks_guard = threading.Lock()
|
|
49
|
+
# Track all instances for cleanup
|
|
50
|
+
_instances: list['AsyncDB'] = []
|
|
49
51
|
|
|
50
52
|
def __init__(self, db_path: Union[str, Path], table_name: str, schema_class: Type[SchemaType]):
|
|
51
53
|
"""Initialize AsyncDB with a path and schema dataclass."""
|
|
@@ -76,6 +78,9 @@ class AsyncDB(Generic[SchemaType]):
|
|
|
76
78
|
# Persistent connection (lazy init)
|
|
77
79
|
self._connection: Optional[aiosqlite.Connection] = None
|
|
78
80
|
|
|
81
|
+
# Track instance for cleanup
|
|
82
|
+
AsyncDB._instances.append(self)
|
|
83
|
+
|
|
79
84
|
# Use a class-level set to track initialized schemas
|
|
80
85
|
if not hasattr(AsyncDB, '_initialized_schemas'):
|
|
81
86
|
AsyncDB._initialized_schemas = set()
|
|
@@ -138,6 +143,13 @@ class AsyncDB(Generic[SchemaType]):
|
|
|
138
143
|
pass
|
|
139
144
|
self._connection = None
|
|
140
145
|
|
|
146
|
+
@classmethod
|
|
147
|
+
async def close_all(cls) -> None:
|
|
148
|
+
"""Close all AsyncDB instances."""
|
|
149
|
+
for instance in cls._instances:
|
|
150
|
+
await instance.close()
|
|
151
|
+
cls._instances.clear()
|
|
152
|
+
|
|
141
153
|
async def _init_schema(self, db: aiosqlite.Connection) -> None:
|
|
142
154
|
"""Generate schema from dataclass structure with support for field additions."""
|
|
143
155
|
logger.debug(f"Initializing schema for {self.schema_class.__name__} in table {self.table_name}")
|
|
@@ -3,6 +3,7 @@ from functools import lru_cache
|
|
|
3
3
|
from typing import TypeAlias, Union, Optional, Dict, Any, AsyncContextManager, Literal
|
|
4
4
|
from urllib.parse import urlparse
|
|
5
5
|
import asyncio
|
|
6
|
+
import atexit
|
|
6
7
|
import json
|
|
7
8
|
import random
|
|
8
9
|
import ssl
|
|
@@ -257,12 +258,32 @@ async def close_shared_client() -> None:
|
|
|
257
258
|
|
|
258
259
|
|
|
259
260
|
async def cleanup_all() -> None:
|
|
260
|
-
"""Close all global HTTP resources (domain clients + cffi session)."""
|
|
261
|
+
"""Close all global HTTP resources (domain clients + cffi session) and DB connections."""
|
|
261
262
|
await close_shared_client()
|
|
262
263
|
if _get_session_cffi.cache_info().currsize > 0:
|
|
263
264
|
cffi_session = _get_session_cffi()
|
|
264
265
|
await cffi_session.close()
|
|
265
266
|
_get_session_cffi.cache_clear()
|
|
267
|
+
# Close all AsyncDB instances
|
|
268
|
+
from esuls.db_cli import AsyncDB
|
|
269
|
+
await AsyncDB.close_all()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _atexit_cleanup() -> None:
|
|
273
|
+
"""Run async cleanup at process exit."""
|
|
274
|
+
try:
|
|
275
|
+
loop = asyncio.get_event_loop()
|
|
276
|
+
if loop.is_running():
|
|
277
|
+
loop.create_task(cleanup_all())
|
|
278
|
+
else:
|
|
279
|
+
loop.run_until_complete(cleanup_all())
|
|
280
|
+
except RuntimeError:
|
|
281
|
+
loop = asyncio.new_event_loop()
|
|
282
|
+
loop.run_until_complete(cleanup_all())
|
|
283
|
+
loop.close()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
atexit.register(_atexit_cleanup)
|
|
266
287
|
|
|
267
288
|
|
|
268
289
|
async def close_domain_client(url: str, http2: Optional[bool] = None) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|