Projeto Estoque com SQLite
A padaria da Ana controla o estoque num caderno: chegou café, risca e escreve o número novo; vendeu bolo, risca de novo. Neste projeto, o caderno vira um programa. Os produtos ficam guardados num banco de dados SQLite, um arquivo estoque.db que continua lá depois que o programa termina, e um menu no terminal permite listar, cadastrar, dar entrada, dar saída e ver um relatório. O programa recusa um produto repetido, não deixa o estoque ficar negativo e não quebra quando alguém digita "quinze" no lugar de 15.
- Abrir um banco, criar a tabela e gravar com o módulo sqlite3:
CREATE TABLE,INSERT,SELECT,UPDATE,DELETE, sempre com o marcador?. - Confirmar as mudanças com
with con:(ocommitautomático). - Separar o programa em funções com parâmetros e return.
- Percorrer o resultado com for e desempacotamento, e alinhar colunas com f-strings.
- Tratar erros com try e except (
IntegrityError,ValueError). - Fazer o menu com while, break e match e case.
O que vamos construir
Este é o programa pronto, no computador da Ana. O que aparece depois de cada pergunta foi digitado por ela (o 1, o Sonho, o quinze…); o resto foi o programa que escreveu:
$ python3 estoque.py 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 1 === Estoque da Padaria Pão Quente === Bolo de milho 4 un. R$ 18.00 Café 500 g 10 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 120 un. R$ 0.80 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 2 Nome do produto: Sonho Quantidade: quinze "quinze" não é um número inteiro. Tente de novo. « texto no lugar de número Quantidade: 15 Preço (R$): 6,50 « com vírgula também vale Cadastrado: Sonho 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 4 Produto: Bolo de milho Quantas saíram? 6 Não dá para tirar 6 de Bolo de milho: só há 4. « o estoque não fica negativo 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 4 Produto: Bolo de milho Quantas saíram? 3 Bolo de milho: 4 -> 1 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 3 Produto: Café 500 g Quantas chegaram? 12 Café 500 g: 10 -> 22 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 5 Valor do estoque: R$ 759.30 Abaixo de 5 unidades: - Bolo de milho (1) 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 9 Opção inválida: 9 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 0 Até amanhã!
E no dia seguinte, quando a Ana abre o programa de novo, tudo continua lá: o Sonho cadastrado, o bolo com 1 unidade, o café com 22. É a diferença entre guardar numa variável (que some quando o programa termina) e guardar num banco:
$ python3 estoque.py 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 1 === Estoque da Padaria Pão Quente === Bolo de milho 1 un. R$ 18.00 Café 500 g 22 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 120 un. R$ 0.80 Sonho 15 un. R$ 6.50 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair Opção: 0 Até amanhã!
Vamos chegar lá em quatro versões. No fim de cada uma o programa roda, e cada versão acrescenta uma coisa só:
- O banco, a tabela e a listagem (
connect,CREATE TABLE,INSERT,SELECT). - Funções e o cadastro sem repetição (
def,with con:,IntegrityError). - Entrada e saída de mercadoria (
UPDATE, sem deixar negativo). - O menu e o relatório (
while,match,sum(), entrada validada).
Antes de começar
Leia antes a aula Python com Banco de Dados (sqlite3): ela explica cada comando SQL usado aqui, com calma. Você também precisa saber criar e rodar um arquivo .py (Rodando um Programa). No seu computador, crie a pasta do projeto; o arquivo vai se chamar estoque.py:
$ mkdir -p projetos/estoque $ cd projetos/estoque
Nas caixas Experimente desta página, o programa cria o estoque.db e o usa de verdade, mas, como em toda caixa do guia, cada Rodar parte do zero: os arquivos da execução anterior somem. Por isso o programa cria o banco e a tabela sozinho, e coloca os produtos iniciais quando a tabela está vazia. No seu computador, o arquivo fica na pasta e os dados se acumulam de uma execução para outra, como nas sessões de terminal desta página.
Versão 1 · O banco, a tabela e a listagem
A primeira versão abre (ou cria) o banco, cria a tabela de produtos se ela ainda não existir, coloca quatro produtos se a tabela estiver vazia e mostra o estoque em colunas.
import sqlite3
con = sqlite3.connect("estoque.db")
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
iniciais = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
iniciais,
)
con.commit()
print(f"Banco novo: {len(iniciais)} produtos cadastrados.")
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
con.close()
Por partes
- sqlite3.connect("estoque.db")
- Abre o banco guardado no arquivo
estoque.db. Se o arquivo não existe, ele é criado, vazio. - CREATE TABLE IF NOT EXISTS produtos (…)
- Cria a tabela só se ela ainda não existir. Sem o
IF NOT EXISTS, a segunda execução daria erro (table produtos already exists). O texto entre três aspas (""") pode ocupar várias linhas. - id INTEGER PRIMARY KEY
- Um número único para cada produto, que o SQLite preenche sozinho: 1, 2, 3…
- nome TEXT NOT NULL UNIQUE
- O nome é um texto obrigatório (
NOT NULL) e não pode se repetir (UNIQUE). Essa regra vai trabalhar a nosso favor na versão 2. - quantidade INTEGER, preco REAL
- Um número inteiro e um número com casas decimais.
- fetchone()[0]
- O
SELECT count(*)devolve uma linha com um valor só, a contagem.fetchone()pega essa linha (uma tupla) e[0]pega o valor de dentro. - executemany(…, iniciais)
- Roda o mesmo
INSERTuma vez para cada tupla da lista. Cada?é preenchido por um item da tupla, na ordem: nome, quantidade, preço. - con.commit()
- Grava de verdade no arquivo. Sem ele, os produtos sumiriam ao fechar o banco.
- for nome, quantidade, preco in con.execute(…)
- Percorre o resultado do
SELECT: cada linha é uma tupla, aberta em três variáveis. - {nome:<20} {quantidade:>4} {preco:>6.2f}
- Alinhamento: o nome à esquerda em 20 posições, a quantidade à direita em 4, o preço à direita em 6 com 2 casas (Formatação). É o que deixa as colunas retas.
Rodando duas vezes no computador da Ana:
$ python3 estoque.py Banco novo: 4 produtos cadastrados. === Estoque da Padaria Pão Quente === Bolo de milho 4 un. R$ 18.00 Café 500 g 10 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 120 un. R$ 0.80 $ python3 estoque.py « a 2ª vez: o banco já existe === Estoque da Padaria Pão Quente === Bolo de milho 4 un. R$ 18.00 Café 500 g 10 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 120 un. R$ 0.80
Na primeira vez, a tabela estava vazia e o programa cadastrou os quatro produtos. Na segunda, a contagem deu 4, o if foi pulado e nada foi cadastrado de novo. Os produtos vieram do arquivo. Dá até para espiar o arquivo com o programa sqlite3 do terminal, sem Python nenhum:
$ sqlite3 estoque.db "SELECT * FROM produtos;" 1|Pão francês|120|0.8 2|Leite integral 1 L|24|5.5 3|Café 500 g|10|18.9 4|Bolo de milho|4|18.0
Lá estão os id que o SQLite preencheu (1 a 4), na ordem em que os produtos foram gravados.
No seu computador, os produtos iniciais só entram quando a tabela está vazia, ou seja, na primeira vez. Se você acrescentar um produto na lista depois disso, ele não aparece: o banco já tem 4 produtos. Para recomeçar do zero, apague o arquivo: rm estoque.db. (No editor do guia isso não acontece, porque cada Rodar já começa sem o arquivo.)
Missão: a padaria começou a vender Sonho: 15 unidades a R$ 6,50. Acrescente o sonho à lista de produtos iniciais (lembre: no Python, o decimal é com ponto) e rode.
import sqlite3
con = sqlite3.connect("estoque.db")
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
iniciais = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
iniciais,
)
con.commit()
print(f"Banco novo: {len(iniciais)} produtos cadastrados.")
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
con.close()
import sqlite3
con = sqlite3.connect("estoque.db")
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
iniciais = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
("Sonho", 15, 6.50),
]
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
iniciais,
)
con.commit()
print(f"Banco novo: {len(iniciais)} produtos cadastrados.")
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
con.close()
Versão 2 · Funções e o cadastro sem repetição
O programa vai crescer, então é hora de separá-lo em funções, cada uma com uma tarefa: abrir_banco, cadastrar e listar. E o cadastro precisa lidar com o caso em que o produto já existe.
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
con = abrir_banco("estoque.db")
cadastrar(con, "Sonho", 15, 6.50)
cadastrar(con, "Café 500 g", 5, 19.90)
listar(con)
con.close()
- INICIAIS
- A lista saiu do meio do programa e foi para o topo. O nome em MAIÚSCULAS é o costume para valores fixos, que o programa só lê (aula Escopo).
- def abrir_banco(arquivo): … return con
- Tudo o que a versão 1 fazia para preparar o banco, agora numa função que devolve a conexão pronta.
- with con:
- Um bloco que faz o
commitsozinho quando termina bem e desfaz tudo (rollback) se der erro no meio. É mais seguro que lembrar de chamarcon.commit()(aula sqlite3). - try: … except sqlite3.IntegrityError:
- Se o nome já existe, a regra
UNIQUEda tabela recusa oINSERTcom umIntegrityError("erro de integridade"). Oexceptpega esse erro e mostra um aviso, em vez de o programa quebrar. - return False / return True
- A função conta a quem chamou se cadastrou ou não. Este programa ainda não usa a resposta, mas ela já está lá para quem precisar.
Rodando no computador da Ana, sobre o banco que a versão 1 criou:
$ python3 estoque.py Cadastrado: Sonho Já existe um produto chamado Café 500 g. === Estoque da Padaria Pão Quente === Bolo de milho 4 un. R$ 18.00 Café 500 g 10 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 120 un. R$ 0.80 Sonho 15 un. R$ 6.50 $ python3 estoque.py Já existe um produto chamado Sonho. « na 2ª vez, o Sonho já estava lá Já existe um produto chamado Café 500 g. === Estoque da Padaria Pão Quente === Bolo de milho 4 un. R$ 18.00 Café 500 g 10 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 120 un. R$ 0.80 Sonho 15 un. R$ 6.50
Quando dá errado na versão 2
try: o nome repetido derruba o programa
Sem o try/except no cadastrar, o segundo cadastro do café para tudo:
$ python3 estoque.py
Cadastrado: Sonho
Traceback (most recent call last):
File "/home/ana/projetos/estoque/estoque.py", line 51, in <module>
cadastrar(con, "Café 500 g", 5, 19.90)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/ana/projetos/estoque/estoque.py", line 33, in cadastrar
con.execute(
~~~~~~~~~~~^
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(nome, quantidade, preco),
^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
sqlite3.IntegrityError: UNIQUE constraint failed: produtos.nome
Leia de baixo para cima: sqlite3.IntegrityError: UNIQUE constraint failed: produtos.nome, "a regra UNIQUE falhou na coluna nome da tabela produtos". O caminho mostra que a linha 51 (o programa principal) chamou cadastrar, e dentro dele o con.execute da linha 33 foi recusado. Como o execute ocupa várias linhas, o Python marca o comando inteiro com ^. O sqlite3. antes do nome quer dizer que esse tipo de erro vem do módulo sqlite3; por isso o except escreve sqlite3.IntegrityError.
Repare que o banco não aceitou o café repetido mesmo sem o try: quem protege os dados é a regra UNIQUE da tabela. O try só faz o programa reagir com educação, em vez de quebrar.
Missão: nesta versão, a função cadastrar está sem o try, e o café repetido derruba o programa. Rode, leia o IntegrityError e proteja o with con: com try/except sqlite3.IntegrityError, mostrando Já existe um produto chamado Café 500 g. e deixando o programa seguir até a listagem.
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
con = abrir_banco("estoque.db")
cadastrar(con, "Sonho", 15, 6.50)
cadastrar(con, "Café 500 g", 5, 19.90)
listar(con)
con.close()
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
con = abrir_banco("estoque.db")
cadastrar(con, "Sonho", 15, 6.50)
cadastrar(con, "Café 500 g", 5, 19.90)
listar(con)
con.close()
Versão 3 · Entrada e saída de mercadoria
Chegou café, vendeu pão: a quantidade muda. Uma função só, movimentar, faz as duas coisas: um número positivo é entrada, um negativo é saída. Antes de gravar, ela confere duas coisas: se o produto existe e se a saída não deixa o estoque negativo. O resto do programa (abrir_banco, cadastrar, listar) é igual ao da versão 2; estas são as partes novas:
def movimentar(con, nome, quantidade):
linha = con.execute(
"SELECT quantidade FROM produtos WHERE nome = ?", (nome,)
).fetchone()
if linha is None:
print(f"Produto não encontrado: {nome}")
return False
atual = linha[0]
nova = atual + quantidade
if nova < 0:
print(f"Não dá para tirar {-quantidade} de {nome}: só há {atual}.")
return False
with con:
con.execute(
"UPDATE produtos SET quantidade = ? WHERE nome = ?", (nova, nome)
)
print(f"{nome}: {atual} -> {nova}")
return True
con = abrir_banco("estoque.db")
movimentar(con, "Pão francês", -30)
movimentar(con, "Café 500 g", 12)
movimentar(con, "Bolo de milho", -6)
movimentar(con, "Pão de mel", 5)
listar(con)
con.close()
- WHERE nome = ?", (nome,)
- Procura a linha do produto. Repare na vírgula em
(nome,): é ela que faz uma tupla de um item só (aula Tuplas). Sem a vírgula, dá erro; veja abaixo. - if linha is None:
- Quando o
SELECTnão acha nada, ofetchone()devolveNone(aula O Valor None): o produto não existe. - nova = atual + quantidade
- Com
-30,120 + (-30)dá 90. A mesma conta serve para entrada e saída. - if nova < 0:
- A saída deixaria o estoque negativo: avisa e sai da função com
return False, sem gravar nada. O{-quantidade}mostra o número sem o sinal de menos. - UPDATE produtos SET quantidade = ? WHERE nome = ?
- Troca a quantidade daquele produto. São dois
?, então a tupla tem dois valores, na mesma ordem:(nova, nome). Sem oWHERE, oUPDATEmudaria todos os produtos.
$ python3 estoque.py Pão francês: 120 -> 90 Café 500 g: 10 -> 22 Não dá para tirar 6 de Bolo de milho: só há 4. Produto não encontrado: Pão de mel === Estoque da Padaria Pão Quente === Bolo de milho 4 un. R$ 18.00 Café 500 g 22 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 90 un. R$ 0.80 Sonho 15 un. R$ 6.50
Quando dá errado na versão 3
Sem o if nova < 0, o programa obedece cegamente, começando com um banco novo:
$ python3 estoque.py Pão francês: 120 -> 90 Café 500 g: 10 -> 22 Bolo de milho: 4 -> -2 « vendeu bolo que não existia Produto não encontrado: Pão de mel === Estoque da Padaria Pão Quente === Bolo de milho -2 un. R$ 18.00 Café 500 g 22 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 90 un. R$ 0.80
Nenhum erro, só um estoque impossível: menos dois bolos. O banco não sabe que quantidade negativa não faz sentido; quem tem de conferir é o programa.
with con: (e o commit)
Se o UPDATE for feito com con.execute(…) solto, sem with con: nem con.commit(), tudo parece funcionar. Mas rode duas vezes, com um banco novo:
$ python3 estoque.py Pão francês: 120 -> 90 Café 500 g: 10 -> 22 Não dá para tirar 6 de Bolo de milho: só há 4. Produto não encontrado: Pão de mel === Estoque da Padaria Pão Quente === Bolo de milho 4 un. R$ 18.00 Café 500 g 22 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 90 un. R$ 0.80 $ python3 estoque.py Pão francês: 120 -> 90 « de novo 120: a venda de ontem sumiu Café 500 g: 10 -> 22 Não dá para tirar 6 de Bolo de milho: só há 4. Produto não encontrado: Pão de mel === Estoque da Padaria Pão Quente === Bolo de milho 4 un. R$ 18.00 Café 500 g 22 un. R$ 18.90 Leite integral 1 L 24 un. R$ 5.50 Pão francês 90 un. R$ 0.80
Dentro de cada execução, a listagem mostra 90 pães. Mas a segunda execução começa de novo com 120: as mudanças ficaram "a lápis" e foram jogadas fora no con.close(). Sem mensagem de erro nenhuma. Como consertar: toda mudança (INSERT, UPDATE, DELETE) dentro de um with con:.
import sqlite3
con = sqlite3.connect("estoque.db")
nome = "Café 500 g"
linha = con.execute("SELECT quantidade FROM produtos WHERE nome = ?", (nome)).fetchone()
print(linha)
$ python3 consulta.py
Traceback (most recent call last):
File "/home/ana/projetos/estoque/consulta.py", line 5, in <module>
linha = con.execute("SELECT quantidade FROM produtos WHERE nome = ?", (nome)).fetchone()
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 10 supplied.
"Número errado de valores: o comando usa 1, e foram fornecidos 10". Dez? Sem a vírgula, (nome) não é uma tupla, é só o texto "Café 500 g" entre parênteses. O sqlite3 então tratou o texto como uma sequência de letras: são 10 caracteres, contando os espaços. Como consertar: (nome,), com a vírgula.
Parece mais fácil escrever o nome direto no comando com uma f-string. Até aparecer um produto com apóstrofo:
import sqlite3
con = sqlite3.connect("estoque.db")
nome = "Biscoito D'Ouro"
linha = con.execute(f"SELECT quantidade FROM produtos WHERE nome = '{nome}'").fetchone()
print(linha)
$ python3 consulta.py
Traceback (most recent call last):
File "/home/ana/projetos/estoque/consulta.py", line 5, in <module>
linha = con.execute(f"SELECT quantidade FROM produtos WHERE nome = '{nome}'").fetchone()
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.OperationalError: near "Ouro": syntax error
O comando montado ficou … WHERE nome = 'Biscoito D'Ouro': o apóstrofo fechou o texto em 'Biscoito D', e o banco tropeçou no Ouro que sobrou. Pior que esse erro é quando alguém digita de propósito um texto que muda o comando: é o SQL injection, explicado na aula sqlite3. Com o ?, o mesmo nome funciona (o None só diz que esse biscoito não está cadastrado):
import sqlite3
con = sqlite3.connect("estoque.db")
nome = "Biscoito D'Ouro"
linha = con.execute("SELECT quantidade FROM produtos WHERE nome = ?", (nome,)).fetchone()
print(linha)
$ python3 consulta.py None
Regra deste projeto: valor que vem de variável entra no SQL sempre por um ?, nunca por f-string.
import sqlite3
con = sqlite3.connect("estoque.db")
print(con.execute("SELECT nome FROM produto").fetchall())
$ python3 consulta.py
Traceback (most recent call last):
File "/home/ana/projetos/estoque/consulta.py", line 4, in <module>
print(con.execute("SELECT nome FROM produto").fetchall())
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.OperationalError: no such table: produto
"Não existe a tabela produto": a tabela se chama produtos, no plural. O Python não confere o texto do SQL; quem descobre o erro é o SQLite, na hora de rodar.
Missão: nesta versão falta a conferência, e o bolo fica com -2. Acrescente na função movimentar, antes do with con:, o teste que recusa a saída quando o estoque ficaria negativo, com a mensagem Não dá para tirar 6 de Bolo de milho: só há 4.
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
def movimentar(con, nome, quantidade):
linha = con.execute(
"SELECT quantidade FROM produtos WHERE nome = ?", (nome,)
).fetchone()
if linha is None:
print(f"Produto não encontrado: {nome}")
return False
atual = linha[0]
nova = atual + quantidade
with con:
con.execute(
"UPDATE produtos SET quantidade = ? WHERE nome = ?", (nova, nome)
)
print(f"{nome}: {atual} -> {nova}")
return True
con = abrir_banco("estoque.db")
movimentar(con, "Pão francês", -30)
movimentar(con, "Café 500 g", 12)
movimentar(con, "Bolo de milho", -6)
movimentar(con, "Pão de mel", 5)
listar(con)
con.close()
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
def movimentar(con, nome, quantidade):
linha = con.execute(
"SELECT quantidade FROM produtos WHERE nome = ?", (nome,)
).fetchone()
if linha is None:
print(f"Produto não encontrado: {nome}")
return False
atual = linha[0]
nova = atual + quantidade
if nova < 0:
print(f"Não dá para tirar {-quantidade} de {nome}: só há {atual}.")
return False
with con:
con.execute(
"UPDATE produtos SET quantidade = ? WHERE nome = ?", (nova, nome)
)
print(f"{nome}: {atual} -> {nova}")
return True
con = abrir_banco("estoque.db")
movimentar(con, "Pão francês", -30)
movimentar(con, "Café 500 g", 12)
movimentar(con, "Bolo de milho", -6)
movimentar(con, "Pão de mel", 5)
listar(con)
con.close()
Versão 4 · O menu e o relatório
Até aqui, as movimentações estavam escritas no próprio programa. Na versão final, a Ana escolhe o que fazer num menu, que se repete até ela digitar 0. Entram também o relatório (o valor total do estoque e os produtos acabando) e duas funções que perguntam um número até a resposta ser válida. Este é o programa completo:
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
MINIMO = 5
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
def movimentar(con, nome, quantidade):
linha = con.execute(
"SELECT quantidade FROM produtos WHERE nome = ?", (nome,)
).fetchone()
if linha is None:
print(f"Produto não encontrado: {nome}")
return False
atual = linha[0]
nova = atual + quantidade
if nova < 0:
print(f"Não dá para tirar {-quantidade} de {nome}: só há {atual}.")
return False
with con:
con.execute(
"UPDATE produtos SET quantidade = ? WHERE nome = ?", (nova, nome)
)
print(f"{nome}: {atual} -> {nova}")
return True
def relatorio(con):
total = con.execute(
"SELECT sum(quantidade * preco) FROM produtos"
).fetchone()[0]
print(f"Valor do estoque: R$ {total:.2f}")
acabando = con.execute(
"SELECT nome, quantidade FROM produtos WHERE quantidade < ? ORDER BY quantidade",
(MINIMO,),
).fetchall()
if acabando:
print(f"Abaixo de {MINIMO} unidades:")
for nome, quantidade in acabando:
print(f" - {nome} ({quantidade})")
else:
print("Nenhum produto acabando.")
def ler_inteiro(pergunta):
while True:
texto = input(pergunta).strip()
try:
return int(texto)
except ValueError:
print(f'"{texto}" não é um número inteiro. Tente de novo.')
def ler_preco(pergunta):
while True:
texto = input(pergunta).strip().replace(",", ".")
try:
return float(texto)
except ValueError:
print(f'"{texto}" não é um preço. Tente de novo.')
def menu(con):
while True:
print()
print("1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair")
opcao = input("Opção: ").strip()
match opcao:
case "1":
listar(con)
case "2":
nome = input("Nome do produto: ").strip()
quantidade = ler_inteiro("Quantidade: ")
preco = ler_preco("Preço (R$): ")
cadastrar(con, nome, quantidade, preco)
case "3":
nome = input("Produto: ").strip()
movimentar(con, nome, ler_inteiro("Quantas chegaram? "))
case "4":
nome = input("Produto: ").strip()
movimentar(con, nome, -ler_inteiro("Quantas saíram? "))
case "5":
relatorio(con)
case "0":
print("Até amanhã!")
break
case _:
print(f"Opção inválida: {opcao}")
con = abrir_banco("estoque.db")
menu(con)
con.close()
As partes novas, uma por uma
- MINIMO = 5
- Abaixo de quantas unidades um produto aparece como "acabando". Fica no topo, com nome, para ser fácil de mudar.
- SELECT sum(quantidade * preco)
- O próprio banco multiplica a quantidade pelo preço de cada produto e soma tudo: o valor do que está na prateleira.
- WHERE quantidade < ? … (MINIMO,)
- Os produtos abaixo do mínimo, do que tem menos para o que tem mais (
ORDER BY quantidade). Ofetchall()devolve uma lista de tuplas; lista vazia conta como falso noif acabando:(aula Booleanos). - ler_inteiro e ler_preco
- Perguntam até receber um número válido: o
while Truerepete, e oreturndentro dotrysai da função assim que a conversão dá certo. Oler_precotroca a vírgula por ponto, para aceitar6,50. É a mesma técnica da versão 4 do Projeto Calculadora. - -ler_inteiro("Quantas saíram? ")
- Na saída, o número digitado vira negativo antes de ir para o
movimentar: a Ana digita 3, a função recebe -3. - match opcao: / case "1": … case _:
- Escolhe o que fazer pela opção digitada (um texto). O
case _pega qualquer outra coisa (aula match e case). - case "0": … break
- Sai do
while True; depois dele, o programa fecha o banco e termina.
A sessão completa está no começo da página, em O que vamos construir. O valor do relatório confere: 1 × 18,00 + 22 × 18,90 + 24 × 5,50 + 120 × 0,80 + 15 × 6,50 = R$ 759,30.
Missão: a função relatorio já está escrita, mas a opção 5 do menu responde "Opção inválida". Ligue a opção 5 à função, no match. A caixa vai digitar 5 e depois 0; o relatório do estoque inicial deve mostrar R$ 489.00 e o bolo de milho acabando.
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
MINIMO = 5
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
def movimentar(con, nome, quantidade):
linha = con.execute(
"SELECT quantidade FROM produtos WHERE nome = ?", (nome,)
).fetchone()
if linha is None:
print(f"Produto não encontrado: {nome}")
return False
atual = linha[0]
nova = atual + quantidade
if nova < 0:
print(f"Não dá para tirar {-quantidade} de {nome}: só há {atual}.")
return False
with con:
con.execute(
"UPDATE produtos SET quantidade = ? WHERE nome = ?", (nova, nome)
)
print(f"{nome}: {atual} -> {nova}")
return True
def relatorio(con):
total = con.execute(
"SELECT sum(quantidade * preco) FROM produtos"
).fetchone()[0]
print(f"Valor do estoque: R$ {total:.2f}")
acabando = con.execute(
"SELECT nome, quantidade FROM produtos WHERE quantidade < ? ORDER BY quantidade",
(MINIMO,),
).fetchall()
if acabando:
print(f"Abaixo de {MINIMO} unidades:")
for nome, quantidade in acabando:
print(f" - {nome} ({quantidade})")
else:
print("Nenhum produto acabando.")
def ler_inteiro(pergunta):
while True:
texto = input(pergunta).strip()
try:
return int(texto)
except ValueError:
print(f'"{texto}" não é um número inteiro. Tente de novo.')
def ler_preco(pergunta):
while True:
texto = input(pergunta).strip().replace(",", ".")
try:
return float(texto)
except ValueError:
print(f'"{texto}" não é um preço. Tente de novo.')
def menu(con):
while True:
print()
print("1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair")
opcao = input("Opção: ").strip()
match opcao:
case "1":
listar(con)
case "2":
nome = input("Nome do produto: ").strip()
quantidade = ler_inteiro("Quantidade: ")
preco = ler_preco("Preço (R$): ")
cadastrar(con, nome, quantidade, preco)
case "3":
nome = input("Produto: ").strip()
movimentar(con, nome, ler_inteiro("Quantas chegaram? "))
case "4":
nome = input("Produto: ").strip()
movimentar(con, nome, -ler_inteiro("Quantas saíram? "))
case "0":
print("Até amanhã!")
break
case _:
print(f"Opção inválida: {opcao}")
con = abrir_banco("estoque.db")
menu(con)
con.close()
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
MINIMO = 5
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
def movimentar(con, nome, quantidade):
linha = con.execute(
"SELECT quantidade FROM produtos WHERE nome = ?", (nome,)
).fetchone()
if linha is None:
print(f"Produto não encontrado: {nome}")
return False
atual = linha[0]
nova = atual + quantidade
if nova < 0:
print(f"Não dá para tirar {-quantidade} de {nome}: só há {atual}.")
return False
with con:
con.execute(
"UPDATE produtos SET quantidade = ? WHERE nome = ?", (nova, nome)
)
print(f"{nome}: {atual} -> {nova}")
return True
def relatorio(con):
total = con.execute(
"SELECT sum(quantidade * preco) FROM produtos"
).fetchone()[0]
print(f"Valor do estoque: R$ {total:.2f}")
acabando = con.execute(
"SELECT nome, quantidade FROM produtos WHERE quantidade < ? ORDER BY quantidade",
(MINIMO,),
).fetchall()
if acabando:
print(f"Abaixo de {MINIMO} unidades:")
for nome, quantidade in acabando:
print(f" - {nome} ({quantidade})")
else:
print("Nenhum produto acabando.")
def ler_inteiro(pergunta):
while True:
texto = input(pergunta).strip()
try:
return int(texto)
except ValueError:
print(f'"{texto}" não é um número inteiro. Tente de novo.')
def ler_preco(pergunta):
while True:
texto = input(pergunta).strip().replace(",", ".")
try:
return float(texto)
except ValueError:
print(f'"{texto}" não é um preço. Tente de novo.')
def menu(con):
while True:
print()
print("1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair")
opcao = input("Opção: ").strip()
match opcao:
case "1":
listar(con)
case "2":
nome = input("Nome do produto: ").strip()
quantidade = ler_inteiro("Quantidade: ")
preco = ler_preco("Preço (R$): ")
cadastrar(con, nome, quantidade, preco)
case "3":
nome = input("Produto: ").strip()
movimentar(con, nome, ler_inteiro("Quantas chegaram? "))
case "4":
nome = input("Produto: ").strip()
movimentar(con, nome, -ler_inteiro("Quantas saíram? "))
case "5":
relatorio(con)
case "0":
print("Até amanhã!")
break
case _:
print(f"Opção inválida: {opcao}")
con = abrir_banco("estoque.db")
menu(con)
con.close()
Formas diferentes de confirmar as mudanças
O projeto usou con.commit() na versão 1 e with con: a partir da versão 2. As duas gravam; a diferença está no que acontece quando dá erro no meio.
| Forma | Quando usar |
|---|---|
con.execute(…) + con.commit() | Funciona, mas depende de você lembrar do commit em todo lugar que muda dados. Um esquecimento e a mudança some, sem aviso |
with con: + con.execute(…) | O preferido: grava sozinho no fim do bloco e, se algo der errado no meio, desfaz tudo do bloco |
executemany(…, lista) | Para gravar várias linhas com o mesmo comando, como os produtos iniciais |
Desafio extra: remover um produto
A padaria parou de vender o bolo de milho. Acrescente a opção 6) Remover, com uma função que apaga o produto com DELETE. Para saber se apagou alguma coisa, use o rowcount ("contagem de linhas") do resultado do execute: é o número de linhas que o comando mudou, 0 quando o produto não existe.
$ python3 estoque.py 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 6) Remover 0) Sair Opção: 6 Produto: Sonho Produto não encontrado: Sonho 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 6) Remover 0) Sair Opção: 6 Produto: Bolo de milho Removido: Bolo de milho 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 6) Remover 0) Sair Opção: 5 Valor do estoque: R$ 417.00 Nenhum produto acabando. 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 6) Remover 0) Sair Opção: 0 Até amanhã!
(Essa sessão começou com um banco novo, só com os quatro produtos iniciais; por isso o Sonho não foi encontrado.)
Missão: escreva a função remover(con, nome) com DELETE FROM produtos WHERE nome = ? dentro de um with con:, mostrando Removido: … ou Produto não encontrado: …, e ligue-a à opção 6 do menu. A caixa vai remover o bolo de milho, tentar remover o pão de mel, listar e sair.
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
MINIMO = 5
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
def movimentar(con, nome, quantidade):
linha = con.execute(
"SELECT quantidade FROM produtos WHERE nome = ?", (nome,)
).fetchone()
if linha is None:
print(f"Produto não encontrado: {nome}")
return False
atual = linha[0]
nova = atual + quantidade
if nova < 0:
print(f"Não dá para tirar {-quantidade} de {nome}: só há {atual}.")
return False
with con:
con.execute(
"UPDATE produtos SET quantidade = ? WHERE nome = ?", (nova, nome)
)
print(f"{nome}: {atual} -> {nova}")
return True
def relatorio(con):
total = con.execute(
"SELECT sum(quantidade * preco) FROM produtos"
).fetchone()[0]
print(f"Valor do estoque: R$ {total:.2f}")
acabando = con.execute(
"SELECT nome, quantidade FROM produtos WHERE quantidade < ? ORDER BY quantidade",
(MINIMO,),
).fetchall()
if acabando:
print(f"Abaixo de {MINIMO} unidades:")
for nome, quantidade in acabando:
print(f" - {nome} ({quantidade})")
else:
print("Nenhum produto acabando.")
def ler_inteiro(pergunta):
while True:
texto = input(pergunta).strip()
try:
return int(texto)
except ValueError:
print(f'"{texto}" não é um número inteiro. Tente de novo.')
def ler_preco(pergunta):
while True:
texto = input(pergunta).strip().replace(",", ".")
try:
return float(texto)
except ValueError:
print(f'"{texto}" não é um preço. Tente de novo.')
def menu(con):
while True:
print()
print("1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 0) Sair")
opcao = input("Opção: ").strip()
match opcao:
case "1":
listar(con)
case "2":
nome = input("Nome do produto: ").strip()
quantidade = ler_inteiro("Quantidade: ")
preco = ler_preco("Preço (R$): ")
cadastrar(con, nome, quantidade, preco)
case "3":
nome = input("Produto: ").strip()
movimentar(con, nome, ler_inteiro("Quantas chegaram? "))
case "4":
nome = input("Produto: ").strip()
movimentar(con, nome, -ler_inteiro("Quantas saíram? "))
case "5":
relatorio(con)
case "0":
print("Até amanhã!")
break
case _:
print(f"Opção inválida: {opcao}")
con = abrir_banco("estoque.db")
menu(con)
con.close()
import sqlite3
INICIAIS = [
("Pão francês", 120, 0.80),
("Leite integral 1 L", 24, 5.50),
("Café 500 g", 10, 18.90),
("Bolo de milho", 4, 18.00),
]
MINIMO = 5
def abrir_banco(arquivo):
con = sqlite3.connect(arquivo)
con.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
quantidade INTEGER NOT NULL,
preco REAL NOT NULL
)
""")
quantos = con.execute("SELECT count(*) FROM produtos").fetchone()[0]
if quantos == 0:
with con:
con.executemany(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
INICIAIS,
)
return con
def cadastrar(con, nome, quantidade, preco):
try:
with con:
con.execute(
"INSERT INTO produtos (nome, quantidade, preco) VALUES (?, ?, ?)",
(nome, quantidade, preco),
)
except sqlite3.IntegrityError:
print(f"Já existe um produto chamado {nome}.")
return False
print(f"Cadastrado: {nome}")
return True
def listar(con):
print("=== Estoque da Padaria Pão Quente ===")
for nome, quantidade, preco in con.execute(
"SELECT nome, quantidade, preco FROM produtos ORDER BY nome"
):
print(f"{nome:<20} {quantidade:>4} un. R$ {preco:>6.2f}")
def movimentar(con, nome, quantidade):
linha = con.execute(
"SELECT quantidade FROM produtos WHERE nome = ?", (nome,)
).fetchone()
if linha is None:
print(f"Produto não encontrado: {nome}")
return False
atual = linha[0]
nova = atual + quantidade
if nova < 0:
print(f"Não dá para tirar {-quantidade} de {nome}: só há {atual}.")
return False
with con:
con.execute(
"UPDATE produtos SET quantidade = ? WHERE nome = ?", (nova, nome)
)
print(f"{nome}: {atual} -> {nova}")
return True
def relatorio(con):
total = con.execute(
"SELECT sum(quantidade * preco) FROM produtos"
).fetchone()[0]
print(f"Valor do estoque: R$ {total:.2f}")
acabando = con.execute(
"SELECT nome, quantidade FROM produtos WHERE quantidade < ? ORDER BY quantidade",
(MINIMO,),
).fetchall()
if acabando:
print(f"Abaixo de {MINIMO} unidades:")
for nome, quantidade in acabando:
print(f" - {nome} ({quantidade})")
else:
print("Nenhum produto acabando.")
def remover(con, nome):
with con:
cursor = con.execute("DELETE FROM produtos WHERE nome = ?", (nome,))
if cursor.rowcount == 0:
print(f"Produto não encontrado: {nome}")
else:
print(f"Removido: {nome}")
def ler_inteiro(pergunta):
while True:
texto = input(pergunta).strip()
try:
return int(texto)
except ValueError:
print(f'"{texto}" não é um número inteiro. Tente de novo.')
def ler_preco(pergunta):
while True:
texto = input(pergunta).strip().replace(",", ".")
try:
return float(texto)
except ValueError:
print(f'"{texto}" não é um preço. Tente de novo.')
def menu(con):
while True:
print()
print("1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Relatório 6) Remover 0) Sair")
opcao = input("Opção: ").strip()
match opcao:
case "1":
listar(con)
case "2":
nome = input("Nome do produto: ").strip()
quantidade = ler_inteiro("Quantidade: ")
preco = ler_preco("Preço (R$): ")
cadastrar(con, nome, quantidade, preco)
case "3":
nome = input("Produto: ").strip()
movimentar(con, nome, ler_inteiro("Quantas chegaram? "))
case "4":
nome = input("Produto: ").strip()
movimentar(con, nome, -ler_inteiro("Quantas saíram? "))
case "5":
relatorio(con)
case "6":
remover(con, input("Produto: ").strip())
case "0":
print("Até amanhã!")
break
case _:
print(f"Opção inválida: {opcao}")
con = abrir_banco("estoque.db")
menu(con)
con.close()
Referência rápida do projeto
| Escrita | Para que serviu |
|---|---|
sqlite3.connect("estoque.db") | Abrir (ou criar) o arquivo do banco |
CREATE TABLE IF NOT EXISTS … | Criar a tabela só na primeira vez |
nome TEXT NOT NULL UNIQUE | Proibir nome vazio e nome repetido |
INSERT … VALUES (?, ?, ?), (a, b, c) | Gravar uma linha; cada ? recebe um valor da tupla, na ordem |
executemany(sql, lista) | Gravar várias linhas de uma vez |
SELECT … ORDER BY nome + for a, b, c in … | Listar, já em ordem |
fetchone() / fetchall() | Uma linha (ou None) / a lista de todas |
UPDATE … SET quantidade = ? WHERE nome = ? | Mudar a quantidade de um produto |
DELETE … WHERE nome = ? + rowcount | Apagar e saber se apagou |
sum(quantidade * preco) | Somar no próprio banco |
with con: | Gravar no fim do bloco, ou desfazer tudo se der erro |
except sqlite3.IntegrityError: | Reagir ao nome repetido |
| Sintoma | Causa provável |
|---|---|
sqlite3.IntegrityError: UNIQUE constraint failed: produtos.nome | Cadastro de um nome que já existe, sem try |
| Estoque negativo, sem erro | Faltou conferir a saída antes do UPDATE |
| A mudança some na execução seguinte | Faltou with con: (ou con.commit()) |
Incorrect number of bindings supplied | A quantidade de valores não bate com a de ?; com um valor só, faltou a vírgula: (nome,) |
OperationalError: near "…": syntax error | SQL montado com f-string quebrou num apóstrofo: use ? |
OperationalError: no such table: … | Nome da tabela digitado errado |
| Produto novo na lista inicial não aparece (no seu computador) | A tabela já tinha produtos: apague o estoque.db para recomeçar |
Exercícios
1. O marcador
Complete o comando com o marcador que recebe o nome:
2. A tupla de um item
Complete o que falta para (nome) virar uma tupla de um item:
3. Só na primeira vez
Complete para que a tabela seja criada só se ainda não existir:
4. A Ana roda a versão 2 duas vezes no computador dela. Por que, na segunda vez, aparece "Já existe um produto chamado Sonho."?
5. O que acontece com um UPDATE feito dentro de with con: quando dá um erro no meio do bloco?
6. Por que o projeto nunca monta o SQL com f-string, como f"… WHERE nome = '{nome}'"?
Resumo
- O banco SQLite é um arquivo (
estoque.db): os dados continuam lá depois que o programa termina. No editor do guia, cada Rodar começa sem ele; por isso o programa cria a tabela e os produtos iniciais sozinho. CREATE TABLE IF NOT EXISTSe "só cadastra os iniciais se a tabela está vazia" deixam o programa rodar quantas vezes for preciso.- Todo valor que vem de variável entra no SQL por um
?, com uma tupla na mesma ordem; com um valor só,(nome,). - Toda mudança vai dentro de
with con:: grava no fim, desfaz se der erro. Sem isso, a mudança some sem aviso. - A regra
UNIQUEprotege os dados; otry/except sqlite3.IntegrityErrorfaz o programa reagir com educação. - O banco guarda o que você mandar: conferir se a saída deixa o estoque negativo é trabalho do programa.
- Funções pequenas, cada uma com uma tarefa (
abrir_banco,cadastrar,movimentar,relatorio), deixam o menu curto e fácil de ler.