https://github.com/dan1elt0m/sadel
SQLModel helper class for upserting records.
https://github.com/dan1elt0m/sadel
alembic asyncio database pydantic sqlalchemy sqlmodel upsert
Last synced: about 1 month ago
JSON representation
SQLModel helper class for upserting records.
- Host: GitHub
- URL: https://github.com/dan1elt0m/sadel
- Owner: dan1elt0m
- License: mit
- Created: 2024-08-01T20:41:29.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-10-14T19:22:57.000Z (4 months ago)
- Last Synced: 2025-10-20T19:26:58.760Z (4 months ago)
- Topics: alembic, asyncio, database, pydantic, sqlalchemy, sqlmodel, upsert
- Language: Python
- Homepage:
- Size: 84 KB
- Stars: 11
- Watchers: 1
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/dan1elt0m/sadel/actions/workflows/codeql-analysis.yml)
[](https://github.com/dan1elt0m/sadel/actions/workflows/dependabot/dependabot-updates)
[](https://github.com/dan1elt0m/sadel/actions/workflows/test.yml)
[](https://codecov.io/github/dan1elt0m/sadel)

# Sadel
Sadel is a helper class for upserting records with [SQLModel](https://sqlmodel.tiangolo.com/).
### Installation
```bash
pip install sadel
```
#### Example upsert
```python
from sadel import Sadel
from sqlalchemy.ext.asyncio import create_async_engine
from sqlmodel import Field, create_engine, select, or_
from sqlmodel.ext.asyncio.session import AsyncSession
class Hero(Sadel, table=True):
__tablename__ = "hero"
_upsert_index_elements = {"id"}
id: int | None = Field(default=None, primary_key=True)
name: str
secret_name: str
age: int | None = None
# Create
sqlite_url_async = "sqlite+aiosqlite:///database.db"
async_engine = create_async_engine(sqlite_url_async, echo=True, future=True)
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
async with AsyncSession(async_engine) as session:
# Upsert the record
await Hero.upsert(hero, session)
# Fetch the upserted record
result = (
(await session.exec(select(Hero).where(Hero.name == "Deadpond")))
.all()
)
print(result)
```
Output:
```text
[Hero(id=1, name='Deadpond', secret_name='Dive Wilson', age=None, created_on=datetime.datetime(2024, 8, 1, 19, 39, 7), modified_on=None)]
```
### Example batch upsert
```python
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
async with AsyncSession(async_engine) as session:
await Hero.batch_upsert([hero_1, hero_2, hero_3], session)
result = (
(
await session.exec(
select(Hero).where(or_(Hero.name == "Deadpond", Hero.name == "Spider-Boy", Hero.name == "Rusty-Man"))
)
)
.all()
)
print(result)
```
Output:
```text
[Hero(id=1, name='Deadpond', secret_name='Dive Wilson',age=None, created_on=datetime.datetime(2024, 8, 1, 19, 39, 7), modified_on=None),
Hero(id=2, name='Spider-Boy", secret_name='Pedro Parqueador',age=None, created_on=datetime.datetime(2024, 8, 1, 19, 39, 7), modified_on=None),
Hero(id=3, name='Rusty-Man', secret_name='Tommy Sharp', age=48, created_on=datetime.datetime(2024, 8, 1, 19, 39, 7), modified_on=None)]
```
### Example update record
```python
async with AsyncSession(async_engine) as session:
# Upsert the record
hero = Hero(name="Deadpond", secret_name="Dive Wilson", age=25)
await Hero.upsert(hero, session)
# Update the record
hero.age = 30
# Upsert the updated record
await Hero.upsert(hero, session)
# Fetch the updated record
result = (
(await session.exec(select(Hero).where(Hero.name == "Deadpond")))
.scalars()
.all()
)
print(result)
```
Output:
```text
[Hero(id=1, name='Deadpond', secret_name='Dive Wilson', age=30, created_on=datetime.datetime(2024, 8, 1, 19, 39, 7), modified_on=datetime.datetime(2024, 8, 1, 19, 39, 8))]
```
### Features
- Upsert and batch_upsert functions.
- For auditing, automatically adds and manages `created_on` and `modified_on` columns to your table (timezones are supported).
- Validates your data before upserting using Pydantic validate_model method (not supported in SQLModel)
- Asyncio
- Compatible with Alembic
- Specify the (PK) columns to use for upserting using `_upsert_index_elements` attribute
- Ignore specific columns from updating using `_upsert_exclude_fields` attribute
### Contributing
- Fork the repository
- Create a new branch
- Make your changes
- Raise a PR
### License
This project is licensed under the terms of the [MIT License](LICENSE)