first add
This commit is contained in:
51
examples/01_sync_crud.py
Normal file
51
examples/01_sync_crud.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Sync CRUD examples using SQLModel and the kit's generic Repository.
|
||||
|
||||
Prereq:
|
||||
- Export SQL_*/PG* env vars to point at your Postgres
|
||||
- Run this file: uv run python examples/01_sync_crud.py
|
||||
"""
|
||||
|
||||
from typing import Optional, List
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
from sqlmodel_pg_kit import create_all, Repository
|
||||
from sqlmodel_pg_kit.db import get_session
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
age: Optional[int] = None
|
||||
|
||||
|
||||
def main():
|
||||
# Ensure tables exist
|
||||
create_all()
|
||||
|
||||
repo = Repository(Hero)
|
||||
|
||||
with get_session() as s:
|
||||
# Create
|
||||
h = repo.create(s, {"name": "Alice", "age": 20})
|
||||
print("Created:", h)
|
||||
|
||||
# Read by id
|
||||
h2 = repo.get(s, h.id)
|
||||
print("Fetched by id:", h2)
|
||||
|
||||
# Update
|
||||
h3 = repo.update(s, h.id, age=21)
|
||||
print("Updated:", h3)
|
||||
|
||||
# List (pagination)
|
||||
page = repo.list(s, page=1, size=5)
|
||||
print("List page=1,size=5 ->", [x.name for x in page])
|
||||
|
||||
# Delete
|
||||
ok = repo.delete(s, h.id)
|
||||
print("Deleted?", ok)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
52
examples/02_bulk_and_filters.py
Normal file
52
examples/02_bulk_and_filters.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Bulk insert and filtering with the generic Repository and SQLModel sessions.
|
||||
|
||||
Run: uv run python examples/02_bulk_and_filters.py
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
from sqlmodel import select, SQLModel, Field
|
||||
|
||||
from sqlmodel_pg_kit import create_all, Repository
|
||||
from sqlmodel_pg_kit.db import get_session
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
age: Optional[int] = None
|
||||
|
||||
|
||||
def main():
|
||||
create_all()
|
||||
|
||||
repo = Repository(Hero)
|
||||
|
||||
# Clean slate (optional) – delete all heroes
|
||||
with get_session() as s:
|
||||
s.exec(select(Hero)) # warm up
|
||||
s.execute(Hero.__table__.delete())
|
||||
s.commit()
|
||||
|
||||
rows = [
|
||||
{"name": "PG Hero", "age": 1},
|
||||
{"name": "PG Hero", "age": 2},
|
||||
{"name": "Bob", "age": 30},
|
||||
{"name": "Carol", "age": 40},
|
||||
]
|
||||
n = repo.bulk_insert(s, rows)
|
||||
print(f"Bulk inserted: {n}")
|
||||
|
||||
# Filter by name using SQLModel/SQLAlchemy expressions
|
||||
with get_session() as s:
|
||||
heroes: List[Hero] = s.exec(select(Hero).where(Hero.name == "PG Hero")).all()
|
||||
print("Filter name='PG Hero' ->", [(h.id, h.age) for h in heroes])
|
||||
|
||||
# Range filtering and ordering
|
||||
with get_session() as s:
|
||||
res = s.exec(select(Hero).where(Hero.age >= 2).order_by(Hero.age.asc())).all()
|
||||
print("Age >= 2 asc ->", [(h.name, h.age) for h in res])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
57
examples/03_relationships.py
Normal file
57
examples/03_relationships.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Demonstrate relationships and joined reads using SQLModel with generic kit.
|
||||
|
||||
Run: uv run python examples/03_relationships.py
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlmodel import SQLModel, select, Field, Relationship
|
||||
|
||||
from sqlmodel_pg_kit import create_all
|
||||
from sqlmodel_pg_kit.db import get_session
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
heroes: List["Hero"] = Relationship(back_populates="team")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
age: Optional[int] = None
|
||||
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
def main():
|
||||
create_all()
|
||||
|
||||
with get_session() as s:
|
||||
# Clean
|
||||
s.execute(Hero.__table__.delete())
|
||||
s.execute(Team.__table__.delete())
|
||||
s.commit()
|
||||
|
||||
# Create Team and Heroes
|
||||
t = Team(name="Avengers")
|
||||
s.add(t)
|
||||
s.commit()
|
||||
s.refresh(t)
|
||||
|
||||
h1 = Hero(name="Thor", age=1500, team_id=t.id)
|
||||
h2 = Hero(name="Hulk", age=49, team_id=t.id)
|
||||
s.add(h1)
|
||||
s.add(h2)
|
||||
s.commit()
|
||||
|
||||
# Query heroes and eager-load team via selectinload
|
||||
stmt = select(Hero).options(selectinload(Hero.team)).order_by(Hero.id.asc())
|
||||
heroes: List[Hero] = s.exec(stmt).all()
|
||||
print([(h.name, h.team.name if h.team else None) for h in heroes])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
40
examples/04_async_crud.py
Normal file
40
examples/04_async_crud.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
Async example using the provided async engine/session and generic AsyncRepository.
|
||||
|
||||
Run: uv run python examples/04_async_crud.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
from sqlmodel import select, SQLModel, Field
|
||||
|
||||
from sqlmodel_pg_kit import create_all, AsyncRepository
|
||||
from sqlmodel_pg_kit.db import get_async_session
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
age: Optional[int] = None
|
||||
|
||||
|
||||
async def amain():
|
||||
# Ensure tables exist (sync helper is fine to call once)
|
||||
create_all()
|
||||
|
||||
repo = AsyncRepository(Hero)
|
||||
|
||||
# Clean and insert using async session
|
||||
async with get_async_session() as s:
|
||||
await s.execute(Hero.__table__.delete())
|
||||
await s.commit()
|
||||
|
||||
await repo.create(s, {"name": "Async Hero", "age": 7})
|
||||
|
||||
res = await s.execute(select(Hero))
|
||||
heroes = res.scalars().all()
|
||||
print([h.name for h in heroes])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(amain())
|
||||
173
examples/05_cheminformatics.py
Normal file
173
examples/05_cheminformatics.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Cheminformatics example: multi-table schema, dataclass interop, CRUD, joins.
|
||||
|
||||
Prereq:
|
||||
- Export SQL_*/PG* env vars to point at your Postgres
|
||||
- Run: uv run python examples/05_cheminformatics.py
|
||||
|
||||
This example does NOT require RDKit; it stores fields you can compute
|
||||
externally (smiles, selfies, qed, sa_score). If RDKit is available, you
|
||||
can compute those before inserting.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlmodel import SQLModel, Field, Relationship, select
|
||||
|
||||
from sqlmodel_pg_kit.db import get_session
|
||||
from sqlmodel_pg_kit import create_all as _create_all # reuse engine + metadata
|
||||
|
||||
|
||||
# --- Models
|
||||
|
||||
|
||||
class MoleculeDataset(SQLModel, table=True):
|
||||
molecule_id: int = Field(foreign_key="molecule.id", primary_key=True)
|
||||
dataset_id: int = Field(foreign_key="dataset.id", primary_key=True)
|
||||
added_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
class Molecule(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
smiles: str = Field(index=True)
|
||||
selfies: Optional[str] = Field(default=None)
|
||||
qed: Optional[float] = Field(default=None, index=True)
|
||||
sa_score: Optional[float] = Field(default=None, index=True)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
datasets: List["Dataset"] = Relationship(back_populates="molecules", link_model=MoleculeDataset)
|
||||
|
||||
|
||||
class Dataset(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
|
||||
molecules: List["Molecule"] = Relationship(back_populates="datasets", link_model=MoleculeDataset)
|
||||
|
||||
|
||||
# --- Dataclass DTO (handy for RDKit pipelines)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MoleculeDTO:
|
||||
smiles: str
|
||||
selfies: Optional[str] = None
|
||||
qed: Optional[float] = None
|
||||
sa_score: Optional[float] = None
|
||||
|
||||
def to_model(self) -> Molecule:
|
||||
return Molecule(
|
||||
smiles=self.smiles,
|
||||
selfies=self.selfies,
|
||||
qed=self.qed,
|
||||
sa_score=self.sa_score,
|
||||
)
|
||||
|
||||
|
||||
def create_all():
|
||||
# Ensure all tables in this example (plus base kit) exist
|
||||
_create_all()
|
||||
|
||||
|
||||
def main():
|
||||
create_all()
|
||||
|
||||
# Clean existing data for a repeatable run
|
||||
with get_session() as s:
|
||||
s.execute(MoleculeDataset.__table__.delete())
|
||||
s.execute(Molecule.__table__.delete())
|
||||
s.execute(Dataset.__table__.delete())
|
||||
s.commit()
|
||||
|
||||
# Create molecules from dataclass (as you would after RDKit computation)
|
||||
mols = [
|
||||
MoleculeDTO(smiles="CCO", selfies=None, qed=0.45, sa_score=2.1),
|
||||
MoleculeDTO(smiles="c1ccccc1", selfies=None, qed=0.76, sa_score=3.5),
|
||||
MoleculeDTO(smiles="CCN(CC)CC", selfies=None, qed=0.62, sa_score=2.8),
|
||||
]
|
||||
with get_session() as s:
|
||||
for dto in mols:
|
||||
s.add(dto.to_model())
|
||||
s.commit()
|
||||
|
||||
# Create datasets and link molecules (many-to-many)
|
||||
with get_session() as s:
|
||||
ds_train = Dataset(name="train")
|
||||
ds_holdout = Dataset(name="holdout")
|
||||
s.add(ds_train)
|
||||
s.add(ds_holdout)
|
||||
s.commit()
|
||||
s.refresh(ds_train)
|
||||
s.refresh(ds_holdout)
|
||||
|
||||
# Link: first two in train, last one in holdout
|
||||
mol_list: List[Molecule] = s.exec(select(Molecule).order_by(Molecule.id.asc())).all()
|
||||
links = [
|
||||
MoleculeDataset(molecule_id=mol_list[0].id, dataset_id=ds_train.id),
|
||||
MoleculeDataset(molecule_id=mol_list[1].id, dataset_id=ds_train.id),
|
||||
MoleculeDataset(molecule_id=mol_list[2].id, dataset_id=ds_holdout.id),
|
||||
]
|
||||
s.add_all(links)
|
||||
s.commit()
|
||||
|
||||
# CRUD: update a descriptor (e.g., refined QED)
|
||||
with get_session() as s:
|
||||
mol = s.exec(select(Molecule).where(Molecule.smiles == "CCO")).one()
|
||||
mol.qed = 0.50
|
||||
mol.updated_at = datetime.utcnow()
|
||||
s.add(mol)
|
||||
s.commit()
|
||||
s.refresh(mol)
|
||||
print("Updated CCO ->", mol.qed)
|
||||
|
||||
# Filtering: typical queries
|
||||
with get_session() as s:
|
||||
# QED threshold and order by SA score
|
||||
hi_qed = s.exec(
|
||||
select(Molecule).where(Molecule.qed >= 0.6).order_by(Molecule.sa_score.asc())
|
||||
).all()
|
||||
print("qed>=0.6 order by sa_score:", [(m.smiles, m.qed, m.sa_score) for m in hi_qed])
|
||||
|
||||
# Pattern search on SMILES (prefix demo; production use proper search)
|
||||
starts_with_cc = s.exec(select(Molecule).where(Molecule.smiles.like("CC%"))).all()
|
||||
print("SMILES like 'CC%':", [m.smiles for m in starts_with_cc])
|
||||
|
||||
# Joins: list molecules with dataset name (eager-load relationships)
|
||||
with get_session() as s:
|
||||
stmt = (
|
||||
select(Molecule)
|
||||
.options(selectinload(Molecule.datasets))
|
||||
.order_by(Molecule.id.asc())
|
||||
)
|
||||
molecules = s.exec(stmt).all()
|
||||
print("with datasets:", [(m.smiles, [d.name for d in m.datasets]) for m in molecules])
|
||||
|
||||
# Join filter: only molecules in 'train'
|
||||
with get_session() as s:
|
||||
stmt = (
|
||||
select(Molecule)
|
||||
.join(MoleculeDataset, Molecule.id == MoleculeDataset.molecule_id)
|
||||
.join(Dataset, Dataset.id == MoleculeDataset.dataset_id)
|
||||
.where(Dataset.name == "train")
|
||||
.order_by(Molecule.id.asc())
|
||||
)
|
||||
train_mols = s.exec(stmt).all()
|
||||
print("in train:", [m.smiles for m in train_mols])
|
||||
|
||||
# Delete: drop a molecule
|
||||
with get_session() as s:
|
||||
target = s.exec(select(Molecule).where(Molecule.smiles == "CCN(CC)CC")).one()
|
||||
s.delete(target)
|
||||
s.commit()
|
||||
left = s.exec(select(Molecule).order_by(Molecule.id.asc())).all()
|
||||
print("after delete:", [m.smiles for m in left])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
120
examples/06_csv_to_sqlmodel.py
Normal file
120
examples/06_csv_to_sqlmodel.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
Import a CSV into the database by auto-generating a SQLModel class.
|
||||
|
||||
Usage:
|
||||
uv run python examples/06_csv_to_sqlmodel.py --csv path/to/file.csv \
|
||||
[--class-name MyTable] [--table-name my_table] \
|
||||
[--sqlite-memory | --sqlite FILE]
|
||||
|
||||
If neither --sqlite-* is given, the script uses Postgres via SQL_* / PG* env vars.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel_pg_kit import create_all
|
||||
from sqlmodel_pg_kit.csv_import import build_model_from_csv, insert_rows, create_indexes
|
||||
from sqlmodel_pg_kit import db
|
||||
from sqlmodel_pg_kit.db import get_session
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
ap = argparse.ArgumentParser("csv-to-sqlmodel")
|
||||
ap.add_argument("--csv", required=True, help="Path to CSV file")
|
||||
ap.add_argument("--class-name", help="Override generated SQLModel class name (default: CSV stem)")
|
||||
ap.add_argument("--table-name", help="Override table name (default: snake_case of class name)")
|
||||
g = ap.add_mutually_exclusive_group()
|
||||
g.add_argument("--sqlite-memory", action="store_true", help="Use in-memory SQLite")
|
||||
g.add_argument("--sqlite", help="Use SQLite file path (e.g., ./demo.db)")
|
||||
ap.add_argument(
|
||||
"--null",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Add custom null sentinel (repeatable). Default includes '',na,nan,none,null",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--type",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Type override mapping NAME=TYPE (TYPE in bool,int,float,str). Repeatable.",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--rename",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Rename mapping OLD=NEW for columns before sanitization. Repeatable.",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--index",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Create B-Tree index on column (repeatable).",
|
||||
)
|
||||
return ap.parse_args()
|
||||
|
||||
|
||||
def maybe_override_engine(args: argparse.Namespace) -> None:
|
||||
if args.sqlite_memory:
|
||||
db.engine = db.create_engine("sqlite:///:memory:", echo=False)
|
||||
elif args.sqlite:
|
||||
db.engine = db.create_engine(f"sqlite:///{args.sqlite}", echo=False)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
maybe_override_engine(args)
|
||||
|
||||
# Parse mappings
|
||||
def parse_kv(items):
|
||||
m = {}
|
||||
for it in items:
|
||||
if "=" not in it:
|
||||
raise SystemExit(f"Invalid mapping '{it}', expected NAME=VALUE")
|
||||
k, v = it.split("=", 1)
|
||||
m[k.strip()] = v.strip()
|
||||
return m
|
||||
|
||||
rename_map = parse_kv(args.rename)
|
||||
type_map_raw = parse_kv(args.type)
|
||||
type_map = {}
|
||||
for k, v in type_map_raw.items():
|
||||
v_lower = v.lower()
|
||||
if v_lower == "bool":
|
||||
type_map[k] = bool
|
||||
elif v_lower == "int":
|
||||
type_map[k] = int
|
||||
elif v_lower == "float":
|
||||
type_map[k] = float
|
||||
elif v_lower == "str":
|
||||
type_map[k] = str
|
||||
else:
|
||||
raise SystemExit(f"Unsupported type '{v}' for column '{k}' (use bool,int,float,str)")
|
||||
|
||||
spec, rows = build_model_from_csv(
|
||||
args.csv,
|
||||
class_name=args.class_name,
|
||||
table_name=args.table_name,
|
||||
null_values=args.null or None,
|
||||
type_overrides=type_map or None,
|
||||
rename_map=rename_map or None,
|
||||
warn_on_nulls=True,
|
||||
)
|
||||
|
||||
# Create table for the generated model
|
||||
create_all()
|
||||
|
||||
# Insert rows
|
||||
with get_session() as s:
|
||||
n = insert_rows(spec.model, rows, s)
|
||||
print(f"Created table '{spec.table_name}' and inserted {n} rows.")
|
||||
|
||||
# Create indexes if requested
|
||||
if args.index:
|
||||
created = create_indexes(spec.model, args.index, db.engine)
|
||||
print("Created indexes:", created)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user