Database Integration

Noventa is deeply integrated with SQLAlchemy to provide a robust and flexible way to interact with your database.

Using the Database Session

Whenever you need to interact with the database within a component's logic, you can use the db object that is automatically passed to your handlers (both load_template_context and action_* functions). This object is a standard Session object from SQLAlchemy.

You can use this session to query, add, update, and delete records as you would with any SQLAlchemy application.

PYTHON
# Example: Querying for a user in a component
from components.user.user_models import User

def load_template_context(request, session, db, **props):
    user_id = request.args.get("id")
    user = db.query(User).filter(User.id == user_id).first()
    return {"user": user}

def action_update_user(request, session, db, **props):
    user_id = request.form.get("user_id")
    user = db.query(User).filter(User.id == user_id).first()
    if user:
        user.name = request.form.get("name")
        db.commit()
    return {"message": "User updated!"}

Configuration

By default, Noventa is configured to use a local SQLite database. This is defined in the config.yaml file at the root of your project.

YAML
database: "sqlite:///./noventa.db"

While SQLite is great for development and simpler applications, Noventa supports any database that is compatible with SQLAlchemy. To switch to another database like PostgreSQL or MySQL, you simply need to update the database configuration in config.yaml with the appropriate connection details.

Defining Models

Noventa uses SQLAlchemy's modern DeclarativeBase approach for defining database models. Models can be placed in two locations:

Component-Specific Models

For models used only within a specific component, create [component_name]_models.py in the component's folder:

PYTHON
# components/user/user_models.py
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(unique=True)
    name: Mapped[str] = mapped_column()

Global Models

For models used across multiple components, place them in ./models/ directory:

PYTHON
# models/global_models.py
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
    pass

class SystemSetting(Base):
    __tablename__ = "system_settings"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    key: Mapped[str] = mapped_column(unique=True)
    value: Mapped[str] = mapped_column()

Database Migrations with Alembic

Noventa includes Alembic for database schema management. Migrations are automatically generated from your model definitions.

Running Migrations

Alembic is pre-configured and detects models in ./components/ and ./models/ directories. Use these commands from your project root:

BASH
# Generate a new migration after changing models
alembic -c migrations/alembic.ini revision --autogenerate -m "Add user table"

# Apply migrations to database
alembic -c migrations/alembic.ini upgrade head

# Rollback last migration
alembic -c migrations/alembic.ini downgrade -1

Migration Files

Migration files are stored in /migrations/versions/ and contain the SQL commands to upgrade or downgrade your database schema.

Database Seeding

After creating tables, populate initial data using seed scripts in ./migrations/seed/:

PYTHON
# migrations/seed/initial_users.py
from sqlalchemy.orm import Session
from components.user.user_models import User

def seed_users(db: Session):
    # Create initial users
    admin = User(email="admin@example.com", name="Admin")
    db.add(admin)
    db.commit()

Run seed scripts after migrations to populate your database with initial data.

Best Practices

  • Use Mapped type annotations for better IDE support
  • Define relationships explicitly between models
  • Keep component models in their respective folders when possible
  • Always run migrations after model changes
  • Use seed scripts for initial/test data