Skip to content

Commit 2d14632

Browse files
authored
Merge pull request #3 from community-of-python/update-and-rename
rename to db-try, sync with internal version
2 parents 648b1b1 + ec121f2 commit 2d14632

19 files changed

+133
-165
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
with:
2121
enable-cache: true
2222
cache-dependency-glob: "**/pyproject.toml"
23-
- run: uv python install 3.10
23+
- run: uv python install 3.13
2424
- run: just install lint-ci
2525

2626
pytest:

Justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ build:
1414

1515
install:
1616
uv lock --upgrade
17-
uv sync --all-extras --frozen
17+
uv sync --all-extras --all-groups --frozen
1818

1919
lint:
2020
uv run --frozen ruff format

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# modern-pg
1+
# db-try
22

33
This library provides retry decorators for sqlalchemy and some helpers
44

5-
Default settings are in [./pg-tools/settings.py](modern_pg/settings.py).
5+
Default settings are in [./db_try/settings.py](db_try/settings.py).
66
You can redefine them by environment variables.

db_try/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from db_try.connections import build_connection_factory
2+
from db_try.decorators import postgres_retry
3+
from db_try.helpers import build_db_dsn, is_dsn_multihost
4+
from db_try.transaction import Transaction
5+
6+
7+
__all__ = [
8+
"Transaction",
9+
"build_connection_factory",
10+
"build_db_dsn",
11+
"is_dsn_multihost",
12+
"postgres_retry",
13+
]

modern_pg/connections.py renamed to db_try/connections.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ async def _connection_factory() -> "ConnectionType":
7171
target_session_attrs=target_session_attrs,
7272
)
7373
return connection # noqa: TRY300
74-
except (TimeoutError, OSError, asyncpg.TargetServerAttributeNotMatched) as exc: # noqa: PERF203
74+
except (TimeoutError, OSError, asyncpg.TargetServerAttributeNotMatched) as exc:
7575
logger.warning("Failed to fetch asyncpg connection from %s, %s", one_host, exc)
7676
msg: typing.Final = f"None of the hosts match the target attribute requirement {target_session_attrs}"
7777
raise asyncpg.TargetServerAttributeNotMatched(msg)

db_try/decorators.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import functools
2+
import logging
3+
import typing
4+
5+
import asyncpg
6+
import tenacity
7+
from sqlalchemy.exc import DBAPIError
8+
9+
from db_try import settings
10+
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
def _retry_handler(exception: BaseException) -> bool:
16+
if (
17+
isinstance(exception, DBAPIError)
18+
and hasattr(exception, "orig")
19+
and isinstance(exception.orig.__cause__, (asyncpg.SerializationError, asyncpg.PostgresConnectionError)) # type: ignore[union-attr]
20+
):
21+
logger.debug("postgres_retry, retrying")
22+
return True
23+
24+
logger.debug("postgres_retry, giving up on retry")
25+
return False
26+
27+
28+
def postgres_retry[**P, T](
29+
func: typing.Callable[P, typing.Coroutine[None, None, T]],
30+
) -> typing.Callable[P, typing.Coroutine[None, None, T]]:
31+
@tenacity.retry(
32+
stop=tenacity.stop_after_attempt(settings.DB_UTILS_RETRIES_NUMBER),
33+
wait=tenacity.wait_exponential_jitter(),
34+
retry=tenacity.retry_if_exception(_retry_handler),
35+
reraise=True,
36+
before=tenacity.before_log(logger, logging.DEBUG),
37+
)
38+
@functools.wraps(func)
39+
async def wrapped_method(*args: P.args, **kwargs: P.kwargs) -> T:
40+
return await func(*args, **kwargs)
41+
42+
return wrapped_method
File renamed without changes.
File renamed without changes.

db_try/settings.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import os
2+
import typing
3+
4+
5+
DB_UTILS_RETRIES_NUMBER: typing.Final = int(os.getenv("DB_UTILS_RETRIES_NUMBER", "3"))

db_try/transaction.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import dataclasses
2+
3+
import typing_extensions
4+
from sqlalchemy.engine.interfaces import IsolationLevel
5+
from sqlalchemy.ext import asyncio as sa_async
6+
7+
8+
@dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
9+
class Transaction:
10+
session: sa_async.AsyncSession
11+
isolation_level: IsolationLevel | None = None
12+
13+
async def __aenter__(self) -> typing_extensions.Self:
14+
if self.isolation_level:
15+
await self.session.connection(execution_options={"isolation_level": self.isolation_level})
16+
17+
if not self.session.in_transaction():
18+
await self.session.begin()
19+
return self
20+
21+
async def __aexit__(self, *args: object, **kwargs: object) -> None:
22+
await self.session.close()
23+
24+
async def commit(self) -> None:
25+
await self.session.commit()
26+
27+
async def rollback(self) -> None:
28+
await self.session.rollback()

0 commit comments

Comments
 (0)