Skip to content

Commit c3e5e4a

Browse files
authored
Merge pull request #5 from community-of-python/readme
update README.md
2 parents 1fb1b8d + d03cd2e commit c3e5e4a

File tree

3 files changed

+350
-3
lines changed

3 files changed

+350
-3
lines changed

AGENTS.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Project Context for Agents
2+
3+
## Project Overview
4+
5+
This is a Python library called `db-try` that provides PostgreSQL and SQLAlchemy utilities, specifically focusing on:
6+
7+
1. **Retry decorators** for handling database connection issues and serialization errors
8+
2. **Connection factory builders** for managing PostgreSQL connections with multiple hosts
9+
3. **DSN (Data Source Name) utilities** for parsing and manipulating database connection strings
10+
4. **Transaction helpers** for managing SQLAlchemy async sessions
11+
12+
The library is built with modern Python practices (3.13+) and uses type hints extensively. It's designed to work with PostgreSQL databases using the asyncpg driver and SQLAlchemy's asyncio extension.
13+
14+
## Key Technologies
15+
16+
- **Python 3.13+**
17+
- **SQLAlchemy** with asyncio extension
18+
- **asyncpg** PostgreSQL driver
19+
- **tenacity** for retry logic
20+
- **uv** for package management and building
21+
- **Docker** for development and testing environments
22+
- **pytest** for testing
23+
- **ruff** and **mypy** for linting and type checking
24+
25+
## Project Structure
26+
27+
```
28+
db_try/
29+
├── __init__.py # Exports all public APIs
30+
├── connections.py # Connection factory builders
31+
├── dsn.py # DSN parsing and manipulation utilities
32+
├── retry.py # Retry decorators for database operations
33+
├── settings.py # Configuration settings
34+
├── transaction.py # Transaction helper classes
35+
└── py.typed # Marker file for type checking
36+
tests/
37+
├── test_connection_factory.py
38+
├── test_dsn.py
39+
├── test_retry.py
40+
├── test_transaction.py
41+
├── conftest.py # pytest configuration
42+
└── __init__.py
43+
```
44+
45+
## Main Components
46+
47+
### Retry Decorators (`retry.py`)
48+
Provides `@postgres_retry` decorator that automatically retries database operations when encountering:
49+
- PostgreSQL connection errors
50+
- Serialization errors
51+
52+
The retry logic uses exponential backoff with jitter and is configurable via environment variables.
53+
54+
### Connection Factory (`connections.py`)
55+
Provides `build_connection_factory()` function that creates connection factories for PostgreSQL databases with support for:
56+
- Multiple fallback hosts
57+
- Randomized host selection
58+
- Target session attributes (read-write vs standby)
59+
60+
### DSN Utilities (`dsn.py`)
61+
Provides functions for:
62+
- `build_db_dsn()`: Parse and modify DSN strings, replacing database names and setting target session attributes
63+
- `is_dsn_multihost()`: Check if a DSN contains multiple hosts
64+
65+
### Transaction Helpers (`transaction.py`)
66+
Provides `Transaction` class that wraps SQLAlchemy AsyncSession with automatic transaction management.
67+
68+
## Building and Running
69+
70+
### Development Environment Setup
71+
```bash
72+
# Install dependencies
73+
just install
74+
75+
# Run tests
76+
just test
77+
78+
# Run linting and type checking
79+
just lint
80+
81+
# Run all checks (default)
82+
just
83+
```
84+
85+
### Docker-based Development
86+
```bash
87+
# Run tests in Docker
88+
just test
89+
90+
# Run shell in Docker container
91+
just sh
92+
```
93+
94+
### Testing
95+
Tests are written using pytest and can be run with:
96+
```bash
97+
# Run all tests
98+
just test
99+
100+
# Run specific test file
101+
just test tests/test_retry.py
102+
103+
# Run tests with coverage
104+
just test --cov=.
105+
```
106+
107+
## Configuration
108+
109+
The library can be configured using environment variables:
110+
111+
- `DB_UTILS_RETRIES_NUMBER`: Number of retry attempts (default: 3)
112+
113+
## Development Conventions
114+
115+
1. **Type Safety**: Strict mypy checking is enforced
116+
2. **Code Style**: Ruff is used for linting with specific rules configured
117+
3. **Testing**: All functionality should have corresponding tests
118+
4. **Async/Await**: All database operations are asynchronous
119+
5. **Documentation**: Public APIs should be documented with docstrings
120+
121+
## Common Tasks
122+
123+
### Adding New Features
124+
1. Implement the feature in the appropriate module
125+
2. Add tests in the corresponding test file
126+
3. Update exports in `__init__.py` if adding public APIs
127+
4. Run `just` to ensure all checks pass
128+
129+
### Modifying Retry Logic
130+
The retry behavior is defined in `retry.py` and uses the tenacity library. Modify the `_retry_handler` function to change which exceptions trigger retries.
131+
132+
### Working with Connections
133+
Connection handling is in `connections.py`. The `build_connection_factory` function handles connecting to PostgreSQL with support for multiple hosts and fallback mechanisms.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 modern-python
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 196 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,199 @@
11
# db-try
22

3-
This library provides retry decorators for sqlalchemy and some helpers
3+
A Python library providing robust retry mechanisms, connection utilities, and transaction helpers for PostgreSQL and SQLAlchemy applications.
44

5-
Default settings are in [./db_try/settings.py](db_try/settings.py).
6-
You can redefine them by environment variables.
5+
## Features
6+
7+
- **Retry Decorators**: Automatic retry logic for transient database errors
8+
- **Connection Factories**: Robust connection handling with multi-host support
9+
- **DSN Utilities**: Flexible Data Source Name parsing and manipulation
10+
- **Transaction Helpers**: Simplified transaction management with automatic cleanup
11+
12+
## Installation
13+
14+
### Using uv
15+
16+
```bash
17+
uv add db-try
18+
```
19+
20+
### Using pip
21+
22+
```bash
23+
pip install db-try
24+
```
25+
26+
## ORM-Based Usage Examples
27+
28+
### 1. Database Operations with Automatic Retry
29+
30+
Protect your database operations from transient failures using ORM models:
31+
32+
```python
33+
import asyncio
34+
import sqlalchemy as sa
35+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
36+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
37+
from db_try import postgres_retry
38+
39+
class User(DeclarativeBase):
40+
__tablename__ = "users"
41+
42+
id: Mapped[int] = mapped_column(primary_key=True)
43+
name: Mapped[str] = mapped_column(sa.String())
44+
email: Mapped[str] = mapped_column(sa.String(), index=True)
45+
46+
# Apply retry logic to ORM operations
47+
@postgres_retry
48+
async def get_user_by_email(session: AsyncSession, email: str) -> User:
49+
return await session.scalar(
50+
sa.select(User).where(User.email == email)
51+
)
52+
53+
54+
async def main():
55+
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/mydb")
56+
async with AsyncSession(engine) as session:
57+
# Automatically retries on connection failures or serialization errors
58+
user = await get_user_by_email(session, "john.doe@example.com")
59+
if user:
60+
print(f"Found user: {user.name}")
61+
62+
asyncio.run(main())
63+
```
64+
65+
### 2. High Availability Database Connections
66+
67+
Set up resilient database connections with multiple fallback hosts:
68+
69+
```python
70+
import sqlalchemy as sa
71+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
72+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
73+
from db_try import build_connection_factory, build_db_dsn
74+
75+
76+
# Configure multiple database hosts for high availability
77+
multi_host_dsn = (
78+
"postgresql://user:password@/"
79+
"myapp_db?"
80+
"host=primary-db:5432&"
81+
"host=secondary-db:5432&"
82+
"host=backup-db:5432"
83+
)
84+
85+
# Build production-ready DSN
86+
dsn = build_db_dsn(
87+
db_dsn=multi_host_dsn,
88+
database_name="production_database",
89+
drivername="postgresql+asyncpg"
90+
)
91+
92+
# Create connection factory with timeout
93+
connection_factory = build_connection_factory(
94+
url=dsn,
95+
timeout=5.0 # 5 second connection timeout
96+
)
97+
98+
# Engine will automatically try different hosts on failure
99+
engine = create_async_engine(dsn, async_creator=connection_factory)
100+
```
101+
102+
### 3. Simplified Transaction Management
103+
104+
Handle database transactions with automatic cleanup using ORM:
105+
106+
```python
107+
import dataclasses
108+
import datetime
109+
import typing
110+
111+
from schemas import AnalyticsEventCreate, AnalyticsEvent
112+
from db_try import Transaction, postgres_retry
113+
114+
from your_service_name.database.tables import EventsTable
115+
from your_service_name.producers.analytics_service_events_producer import AnalyticsEventsProducer
116+
from your_service_name.repositories.events_repository import EventsRepository
117+
from your_service_name.settings import settings
118+
119+
120+
@dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
121+
class CreateEventUseCase:
122+
events_repository: EventsRepository
123+
transaction: Transaction
124+
analytics_events_producer: AnalyticsEventsProducer
125+
126+
@postgres_retry
127+
async def __call__(
128+
self,
129+
event_create_data: AnalyticsEventCreate,
130+
) -> AnalyticsEvent:
131+
async with self.transaction:
132+
model: typing.Final = EventsTable(
133+
**event_create_data.model_dump(),
134+
created_at=datetime.datetime.now(tz=settings.common.default_timezone),
135+
)
136+
saved_event: typing.Final[EventsTable] = await self.events_repository.create(model)
137+
event: typing.Final = AnalyticsEvent.model_validate(saved_event)
138+
await self.analytics_events_producer.send_message(event)
139+
await self.transaction.commit()
140+
return event
141+
142+
```
143+
144+
### 4. Serializable Transactions for Consistency
145+
146+
Use serializable isolation level to prevent race conditions with ORM:
147+
148+
```python
149+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
150+
from db_try import Transaction
151+
152+
153+
async def main():
154+
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/mydb")
155+
156+
async with AsyncSession(engine) as session:
157+
strict_transaction = Transaction(
158+
session=session,
159+
isolation_level="SERIALIZABLE",
160+
)
161+
# use strict_transaction where needed
162+
```
163+
164+
## Configuration
165+
166+
The library can be configured using environment variables:
167+
168+
| Variable | Description | Default |
169+
|---------------------------|--------------------------------------------------|---------|
170+
| `DB_UTILS_RETRIES_NUMBER` | Number of retry attempts for database operations | 3 |
171+
172+
Example:
173+
```bash
174+
export DB_UTILS_RETRIES_NUMBER=5
175+
```
176+
177+
## API Reference
178+
179+
### Retry Decorator
180+
- `@postgres_retry` - Decorator for async functions that should retry on database errors
181+
182+
### Connection Utilities
183+
- `build_connection_factory(url, timeout)` - Creates a connection factory for multi-host setups
184+
- `build_db_dsn(db_dsn, database_name, use_replica=False, drivername="postgresql")` - Builds a DSN with specified parameters
185+
- `is_dsn_multihost(db_dsn)` - Checks if a DSN contains multiple hosts
186+
187+
### Transaction Helper
188+
- `Transaction(session, isolation_level=None)` - Context manager for simplified transaction handling
189+
190+
## Requirements
191+
192+
- Python 3.13+
193+
- SQLAlchemy with asyncio support
194+
- asyncpg PostgreSQL driver
195+
- tenacity for retry logic
196+
197+
## License
198+
199+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

0 commit comments

Comments
 (0)