DocsUnidade 2: dados

Crie um aplicativo de notas com Nexy, parte 5: armazene as notas em SQLite

A Parte 5 move as notas de um dicionário Python para um banco de dados SQLite, com um modelo, uma migração e duas notas para começar, e as páginas as leem a partir daí.

Antes de começar: o aplicativo da parte 4.

Instale o ORM e a ferramenta de migração

uv add sqlmodel alembic

SQLModel descreve uma tabela como uma classe Python e Alembic transforma alterações nessas classes em migrações. Nexy também não embrulha: você os usa como estão.

Abra o banco de dados

O endereço do banco de dados vai em .env, na raiz do projeto. nx migrate e o aplicativo leem a partir daí:

.env
DATABASE_URL=sqlite:///dev.db

sqlite:///dev.db é um arquivo, dev.db, próximo a nexyconfig.py: SQLite não precisa de servidor. Mantenha .env fora do Git: ele contém os segredos de um banco de dados real.

O mecanismo é a conexão com o banco de dados. Ele lê DATABASE_URL, com o mesmo arquivo como padrão quando a variável não está definida:

src/configs/db.py
import os from dotenv import load_dotenvfrom sqlmodel import create_engine load_dotenv() engine = create_engine(os.getenv("DATABASE_URL", "sqlite:///dev.db"))

Descreva uma nota

src/models/note_model.py
from sqlmodel import Field, SQLModel  class Note(SQLModel, table=True):    id: int | None = Field(default=None, primary_key=True)    text: str

O modelo convive com o recurso ao qual pertence, junto ao seu controlador:

src/apps/notes/note_model.py
from sqlmodel import Field, SQLModel  class Note(SQLModel, table=True):    id: int | None = Field(default=None, primary_key=True)    text: str

table=True transforma a classe em uma tabela, note. O id é fornecido pelo banco de dados quando uma nota é salva.

Crie a tabela com uma migração

nx migrate --initnx migrate -m "create notes"nx migrate --upgrade

--init escreve a pasta alembic/ e aponta para o seu modelo. -m compara o modelo com o banco de dados e escreve a migração que cria a tabela; --upgrade executa. Altere o modelo posteriormente, execute os dois últimos comandos novamente e a tabela segue.

Leia as notas do banco de dados

As funções de src/notes.py mantêm seus nomes, então as páginas mudam pouco: elas agora abrem uma sessão, consultam a tabela e retornam objetos Note em vez de strings.

src/notes.py
from sqlmodel import Session, select from src.configs.db import enginefrom src.models.note_model import Note  def all_notes() -> list[Note]:    with Session(engine) as session:        return list(session.exec(select(Note).order_by(Note.id)))  def find(note_id: int) -> Note | None:    with Session(engine) as session:        return session.get(Note, note_id)
src/notes.py
from sqlmodel import Session, select from src.apps.notes.note_model import Notefrom src.configs.db import engine  def all_notes() -> list[Note]:    with Session(engine) as session:        return list(session.exec(select(Note).order_by(Note.id)))  def find(note_id: int) -> Note | None:    with Session(engine) as session:        return session.get(Note, note_id)

Adicione duas notas para começar

Um script inicial preenche um banco de dados vazio, então cada nova cópia do projeto começa com algo para mostrar:

src/seed.py
from sqlmodel import Session, select from src.configs.db import enginefrom src.models.note_model import Note  def seed() -> None:    with Session(engine) as session:        if session.exec(select(Note)).first() is None:            session.add_all([Note(text="Buy milk"), Note(text="Ship Nexy")])            session.commit()  if __name__ == "__main__":    seed()
src/seed.py
from sqlmodel import Session, select from src.apps.notes.note_model import Notefrom src.configs.db import engine  def seed() -> None:    with Session(engine) as session:        if session.exec(select(Note)).first() is None:            session.add_all([Note(text="Buy milk"), Note(text="Ship Nexy")])            session.commit()  if __name__ == "__main__":    seed()
python -m src.seed

Na inicialização, Nexy importa cada módulo de src/ para encontrar as ações de parte 6. if __name__ == "__main__": mantém a semente do comando acima: importado pelo app, o arquivo apenas define seed e não toca em nada.

Mostrar as notas do banco de dados

src/rotas/notes/index.nexy
---from "@/components/NoteCard.nexy" import NoteCardfrom src.notes import all_notes notes = all_notes()---<section class="app-page">  <h1>Notes</h1>  {% for note in notes %}  <NoteCard id="{{ note.id }}" text="{{ note.text }}" />  {% endfor %}</section>
src/rotas/notas/[id].nexy
---from nexy import notFoundfrom src.notes import find id: prop[int]note = find(id)if note is None:    notFound()---<section class="app-page">  <h1>{{ note.text }}</h1>  <a href="/notes">All notes</a></section>
src/routes/api/notes.py
from src.notes import all_notes  async def GET():    return [note.model_dump() for note in all_notes()]
src/apps/notes/notes_view.nexy
---from "@/components/NoteCard.nexy" import NoteCardfrom src.notes import all_notes notes = all_notes()---<section class="app-page">  <h1>Notes</h1>  {% for note in notes %}  <NoteCard id="{{ note.id }}" text="{{ note.text }}" />  {% endfor %}</section>
src/apps/notes/notes_controller.py
from nexy import notFoundfrom nexy.decorators import Controllerfrom nexy.hooks import useViewsfrom src.notes import all_notes, find LAYOUT = "src/apps/layout.nexy"  def page(view: str, **context: object):    return useViews(LAYOUT, context={"children": useViews(view, context=context)})  @Controller("/notes")class NotesController:    async def get(self):        return page("src/apps/notes/notes_view.nexy")  @Controller("/notes/{id}")class NoteController:    async def get(self, id: int):        note = find(id)        if note is None:            notFound()        return page("src/apps/notes/note_view.nexy", text=note.text)  @Controller("/api/notes")class NotesApiController:    async def get(self) -> list[dict]:        return [note.model_dump() for note in all_notes()]

Abra http://localhost:3000/notes: os mesmos dois cartões, agora lidos em dev.db. Reinicie nexy dev e eles ainda estarão lá.

O que você aprendeu

  • SQLModel descreve uma tabela como uma classe; o mecanismo em src/configs/db.py abre o banco de dados.
  • nx migrate escreve e executa as migrações: --init uma vez, depois -m e --upgrade por alteração.
  • Páginas e controladores chamam funções Python simples que consultam o banco de dados: Nexy não adiciona nenhuma camada.

Próximas etapas

Editar no GitHub

Enter abre o resultado, Esc fecha.