Skip to content

Commit 7ecfb44

Browse files
committed
v4.0.0
1 parent d0a7b24 commit 7ecfb44

10 files changed

Lines changed: 170 additions & 529 deletions

File tree

ddcdatabases/core/base.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
import sqlalchemy as sa
44
from .configs import BaseOperationRetryConfig, BaseRetryConfig
55
from .retry import retry_operation, retry_operation_async
6-
from abc import ABC, abstractmethod
7-
from contextlib import AbstractAsyncContextManager, AbstractContextManager
6+
from collections.abc import AsyncGenerator, Generator
7+
from contextlib import asynccontextmanager, contextmanager
88
from datetime import datetime
9-
from sqlalchemy.engine import URL, Engine
10-
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
9+
from sqlalchemy.engine import URL, Engine, create_engine
10+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
1111
from sqlalchemy.orm import Session, sessionmaker
1212
from typing import Any
1313

1414
_logger = logging.getLogger(__name__)
1515
_logger.addHandler(logging.NullHandler())
1616

1717

18-
class BaseConnection(ABC):
18+
class BaseConnection:
1919
__slots__ = (
2020
"connection_url",
2121
"engine_args",
@@ -109,13 +109,19 @@ async def __aexit__(
109109
self.is_connected = False
110110
self.logger.debug("Disconnected")
111111

112-
@abstractmethod
113-
def _get_engine(self) -> AbstractContextManager[Engine]:
114-
pass
115-
116-
@abstractmethod
117-
def _get_async_engine(self) -> AbstractAsyncContextManager[AsyncEngine]:
118-
pass
112+
@contextmanager
113+
def _get_engine(self) -> Generator[Engine, None, None]:
114+
_connection_url = URL.create(drivername=self.sync_driver, **self.connection_url)
115+
_engine = create_engine(url=_connection_url, **self.engine_args)
116+
yield _engine
117+
_engine.dispose()
118+
119+
@asynccontextmanager
120+
async def _get_async_engine(self) -> AsyncGenerator[AsyncEngine, None]:
121+
_connection_url = URL.create(drivername=self.async_driver, **self.connection_url)
122+
_engine = create_async_engine(url=_connection_url, **self.engine_args)
123+
yield _engine
124+
await _engine.dispose()
119125

120126
def _test_connection_sync(self, session: Session) -> None:
121127
_connection_url_copy = self.connection_url.copy()

ddcdatabases/core/configs.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,50 @@
1+
import dataclasses
12
from dataclasses import dataclass
3+
from typing import Any, TypeVar
4+
5+
_C = TypeVar("_C")
6+
7+
# Field maps for merging retry configs with settings
8+
CONNECTION_RETRY_FIELD_MAP: dict[str, str] = {
9+
"enable_retry": "connection_enable_retry",
10+
"max_retries": "connection_max_retries",
11+
"initial_retry_delay": "connection_initial_retry_delay",
12+
"max_retry_delay": "connection_max_retry_delay",
13+
}
14+
15+
OPERATION_RETRY_FIELD_MAP: dict[str, str] = {
16+
"enable_retry": "operation_enable_retry",
17+
"max_retries": "operation_max_retries",
18+
"initial_retry_delay": "operation_initial_retry_delay",
19+
"max_retry_delay": "operation_max_retry_delay",
20+
"jitter": "operation_jitter",
21+
}
22+
23+
24+
def merge_config_with_settings(
25+
config_cls: type[_C],
26+
override: _C | None,
27+
settings: Any,
28+
field_map: dict[str, str] | None = None,
29+
) -> _C:
30+
"""Create config instance, using override values when not None, else settings defaults.
31+
32+
Args:
33+
config_cls: The dataclass class to instantiate
34+
override: Optional override instance (or None to use all defaults)
35+
settings: Settings object with default values
36+
field_map: Dict mapping config field names to settings attribute names.
37+
If None, config field names must match settings attribute names.
38+
"""
39+
override = override or config_cls()
40+
field_map = field_map or {}
41+
kwargs = {}
42+
for field in dataclasses.fields(config_cls): # type: ignore[arg-type]
43+
name = field.name
44+
settings_attr = field_map.get(name, name)
45+
val = getattr(override, name)
46+
kwargs[name] = val if val is not None else getattr(settings, settings_attr)
47+
return config_cls(**kwargs)
248

349

450
def _validate_retry_config(

ddcdatabases/core/persistent.py

Lines changed: 13 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import threading
1414
import time
1515
import weakref
16-
from .configs import BaseOperationRetryConfig, BaseRetryConfig
16+
from .configs import BaseOperationRetryConfig, BaseRetryConfig, merge_config_with_settings
1717
from .retry import retry_operation, retry_operation_async
1818
from .settings import (
1919
get_mongodb_settings,
@@ -49,6 +49,13 @@ class PersistentConnectionConfig:
4949
auto_reconnect: bool | None = None
5050

5151

52+
_PERSISTENT_CONFIG_FIELD_MAP: dict[str, str] = {
53+
"idle_timeout": "persistent_idle_timeout",
54+
"health_check_interval": "persistent_health_check_interval",
55+
"auto_reconnect": "persistent_auto_reconnect",
56+
}
57+
58+
5259
# Global registry for persistent connections (weak references to allow cleanup)
5360
_persistent_connections: weakref.WeakValueDictionary[str, BasePersistentConnection | PersistentMongoDBConnection] = (
5461
weakref.WeakValueDictionary()
@@ -772,19 +779,7 @@ def __new__(
772779
if schema and schema != "public":
773780
connection_key += f"?schema={schema}"
774781

775-
# Build config from settings, allowing partial overrides
776-
_cfg = config or PersistentConnectionConfig()
777-
config = PersistentConnectionConfig(
778-
idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout,
779-
health_check_interval=(
780-
_cfg.health_check_interval
781-
if _cfg.health_check_interval is not None
782-
else _settings.persistent_health_check_interval
783-
),
784-
auto_reconnect=(
785-
_cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect
786-
),
787-
)
782+
config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP)
788783

789784
# Build SSL connect_args from settings
790785
ssl_mode = _settings.ssl_mode
@@ -960,19 +955,7 @@ def __new__(
960955
database = database or _settings.database
961956
connection_key = f"mysql://{user}@{host}:{port}/{database}" # NOSONAR
962957

963-
# Build config from settings, allowing partial overrides
964-
_cfg = config or PersistentConnectionConfig()
965-
config = PersistentConnectionConfig(
966-
idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout,
967-
health_check_interval=(
968-
_cfg.health_check_interval
969-
if _cfg.health_check_interval is not None
970-
else _settings.persistent_health_check_interval
971-
),
972-
auto_reconnect=(
973-
_cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect
974-
),
975-
)
958+
config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP)
976959

977960
# Build SSL connect_args from settings
978961
ssl_mode = _settings.ssl_mode
@@ -1121,19 +1104,7 @@ def __new__(
11211104
database = database or _settings.database
11221105
connection_key = f"mssql://{user}@{host}:{port}/{database}" # NOSONAR
11231106

1124-
# Build config from settings, allowing partial overrides
1125-
_cfg = config or PersistentConnectionConfig()
1126-
config = PersistentConnectionConfig(
1127-
idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout,
1128-
health_check_interval=(
1129-
_cfg.health_check_interval
1130-
if _cfg.health_check_interval is not None
1131-
else _settings.persistent_health_check_interval
1132-
),
1133-
auto_reconnect=(
1134-
_cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect
1135-
),
1136-
)
1107+
config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP)
11371108

11381109
# Build SSL query params from settings
11391110
_query: dict[str, str] = {"driver": "ODBC Driver 18 for SQL Server"}
@@ -1227,19 +1198,7 @@ def __new__(
12271198
servicename = servicename or _settings.servicename
12281199
connection_key = f"oracle://{user}@{host}:{port}/{servicename}" # NOSONAR
12291200

1230-
# Build config from settings, allowing partial overrides
1231-
_cfg = config or PersistentConnectionConfig()
1232-
config = PersistentConnectionConfig(
1233-
idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout,
1234-
health_check_interval=(
1235-
_cfg.health_check_interval
1236-
if _cfg.health_check_interval is not None
1237-
else _settings.persistent_health_check_interval
1238-
),
1239-
auto_reconnect=(
1240-
_cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect
1241-
),
1242-
)
1201+
config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP)
12431202

12441203
with _registry_lock:
12451204
if connection_key in _persistent_connections:
@@ -1301,19 +1260,7 @@ def __new__(
13011260
database = database or _settings.database
13021261
connection_key = f"mongodb://{user}@{host}:{port}/{database}" # NOSONAR
13031262

1304-
# Build config from settings, allowing partial overrides
1305-
_cfg = config or PersistentConnectionConfig()
1306-
config = PersistentConnectionConfig(
1307-
idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout,
1308-
health_check_interval=(
1309-
_cfg.health_check_interval
1310-
if _cfg.health_check_interval is not None
1311-
else _settings.persistent_health_check_interval
1312-
),
1313-
auto_reconnect=(
1314-
_cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect
1315-
),
1316-
)
1263+
config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP)
13171264

13181265
with _registry_lock:
13191266
if connection_key in _persistent_connections:

0 commit comments

Comments
 (0)