📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Módulo 02: Lógica de Programação e Algoritmos. Recomendado revisar antes de começar.
Trilha de Especialização — Nível Introdutório (Curso 25: Sistemas Desktop com Python)
- Visão Geral dos 10 Miniprojetos: 🌱 Guia Pedagógico de Miniprojetos
- Catálogo Geral: 🏗️ Catálogo de Projetos
- ➡️ Implementação Prática: Miniprojeto 10 (Código Completo em 4 Degraus)
Em escritórios de contabilidade, advocacia e pequenas empresas locais, a rotina de segurança digital exige catalogar arquivos de clientes (NF-e, contratos, folhas de pagamento, relatórios fiscais), copiar os dados para discos externos ou pastas de rede e registrar logs de auditoria detalhados.
Neste projeto, o estudante aprende como:
os e shutil, calculando tamanhos de arquivos e gerando logs de texto (backup_log.txt).ArquivoInfo e GerenciadorBackup para manipular arquivos como objetos de negócio.execucoes_backup 1:N arquivos_backup), auditando cada transferência.filedialog), barra de progresso visual em tempo real (ttk.Progressbar) e histórico em tabela.flowchart LR
D1["🪜 DEGRAU 1\nImperativo & os/shutil\nVarredura de pastas e\nlog em backup_log.txt"] --> D2["🪜 DEGRAU 2\nPrimeira Classe POO\nClasses ArquivoInfo e\nGerenciadorBackup"]
D2 --> D3["🪜 DEGRAU 3\nSQLite com Auditoria\nTabelas execucoes e\narquivos (1:N)"]
D3 --> D4["🪜 DEGRAU 4\nInterface Desktop\nSeleção de pastas, cards,\nprogressbar e histórico"]
style D1 fill:#fffde7,stroke:#fbc02d,stroke-width:2px
style D2 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style D3 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
style D4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
sistemas_python_intro_10_backup_arquivos/
├── degrau_1_imperativo_txt/
│ ├── main.py # Script imperativo com menu, os/shutil e backup_log.txt
│ └── test_degrau_1.py # Testes automatizados de cópia e log
├── degrau_2_primeira_classe/
│ ├── main.py # Classes POO (class ArquivoInfo e class GerenciadorBackup)
│ └── test_degrau_2.py # Testes unitários de manipulação de objetos e volume
├── degrau_3_sqlite/
│ ├── main.py # SQLite3 relacional com auditoria detalhada de cada arquivo
│ └── test_degrau_3.py # Testes unitários de persistência e métricas SQL
├── degrau_4_interface_customtkinter/
│ └── main.py # Aplicação Desktop completa com seleção de pastas e Progressbar
├── main.py # Hub Central / Launcher interativo
└── index.md # Este guia didático autoguiado
No VS Code / PyCharm, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd sistemas_python_intro_10_backup_arquivos
# Executar o Hub Central (Menu interativo com todos os degraus):
python main.py
# Ou executar um degrau individualmente:
python degrau_1_imperativo_txt/main.py
python degrau_2_primeira_classe/main.py
python degrau_3_sqlite/main.py
python degrau_4_interface_customtkinter/main.py
[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code, basta executar:
python catalogo_projetos/sistemas_python_intro_10_backup_arquivos/main.py
os e shutilNo primeiro degrau, exploramos funções do sistema operacional para varrer diretórios, obter o tamanho de arquivos e copiá-los preservando metadados.
degrau_1_imperativo_txt/main.py):import os
import shutil
from datetime import datetime
def catalogar_pasta(pasta_origem):
if not os.path.exists(pasta_origem):
return []
arquivos = []
for item in os.listdir(pasta_origem):
caminho_completo = os.path.join(pasta_origem, item)
if os.path.isfile(caminho_completo):
tamanho = os.path.getsize(caminho_completo)
arquivos.append({"nome": item, "caminho": caminho_completo, "tamanho_bytes": tamanho})
return arquivos
def executar_backup(pasta_origem, pasta_destino, caminho_log="backup_log.txt"):
arquivos = catalogar_pasta(pasta_origem)
os.makedirs(pasta_destino, exist_ok=True)
total_bytes = 0
with open(caminho_log, "a", encoding="utf-8") as f_log:
f_log.write(f"=== BACKUP: {datetime.now()} ===\n")
for arq in arquivos:
shutil.copy2(arq["caminho"], os.path.join(pasta_destino, arq["nome"]))
total_bytes += arq["tamanho_bytes"]
f_log.write(f" - {arq['nome']} ({arq['tamanho_bytes']} bytes)\n")
return len(arquivos), total_bytes
degrau_1_imperativo_txt/test_degrau_1.py):"""
Testes Unitários para o Degrau 1 do Projeto 10 (Imperativo & OS/Shutil)
"""
import os
import shutil
import unittest
from main import (
catalogar_pasta,
executar_backup,
formatar_tamanho
)
PASTA_ORIGEM_TESTE = "teste_origem_temp"
PASTA_DESTINO_TESTE = "teste_destino_temp"
ARQUIVO_LOG_TESTE = "teste_backup_log.txt"
class TestDegrau1Backup(unittest.TestCase):
def setUp(self):
os.makedirs(PASTA_ORIGEM_TESTE, exist_ok=True)
if os.path.exists(PASTA_DESTINO_TESTE):
shutil.rmtree(PASTA_DESTINO_TESTE)
if os.path.exists(ARQUIVO_LOG_TESTE):
os.remove(ARQUIVO_LOG_TESTE)
# Cria arquivos fictícios para backup
with open(os.path.join(PASTA_ORIGEM_TESTE, "contrato_01.pdf"), "w") as f:
f.write("Conteudo do contrato 01")
with open(os.path.join(PASTA_ORIGEM_TESTE, "nfe_1020.xml"), "w") as f:
f.write("Conteudo do xml da nota fiscal")
def tearDown(self):
if os.path.exists(PASTA_ORIGEM_TESTE):
shutil.rmtree(PASTA_ORIGEM_TESTE)
if os.path.exists(PASTA_DESTINO_TESTE):
shutil.rmtree(PASTA_DESTINO_TESTE)
if os.path.exists(ARQUIVO_LOG_TESTE):
os.remove(ARQUIVO_LOG_TESTE)
def test_catalogacao_e_backup(self):
arquivos = catalogar_pasta(PASTA_ORIGEM_TESTE)
self.assertEqual(len(arquivos), 2)
total_copiados, total_bytes = executar_backup(PASTA_ORIGEM_TESTE, PASTA_DESTINO_TESTE, caminho_log=ARQUIVO_LOG_TESTE)
self.assertEqual(total_copiados, 2)
self.assertGreater(total_bytes, 0)
# Verifica se os arquivos foram realmente copiados para o destino
self.assertTrue(os.path.exists(os.path.join(PASTA_DESTINO_TESTE, "contrato_01.pdf")))
self.assertTrue(os.path.exists(os.path.join(PASTA_DESTINO_TESTE, "nfe_1020.xml")))
# Verifica criação do log
self.assertTrue(os.path.exists(ARQUIVO_LOG_TESTE))
if __name__ == "__main__":
unittest.main()
ArquivoInfo e GerenciadorBackup)No segundo degrau, a classe ArquivoInfo formata seu tamanho e a classe GerenciadorBackup coordena a leitura e cópia de lotes de arquivos.
degrau_2_primeira_classe/main.py):class ArquivoInfo:
def __init__(self, caminho, nome, tamanho_bytes):
self.caminho = caminho
self.nome = nome
self.tamanho_bytes = int(tamanho_bytes)
def tamanho_formatado(self):
if self.tamanho_bytes < 1024:
return f"{self.tamanho_bytes} B"
elif self.tamanho_bytes < 1024 * 1024:
return f"{self.tamanho_bytes / 1024:.2f} KB"
return f"{self.tamanho_bytes / (1024 * 1024):.2f} MB"
class GerenciadorBackup:
def __init__(self, pasta_origem, pasta_destino):
self.pasta_origem = pasta_origem
self.pasta_destino = pasta_destino
self.arquivos = []
def escanear(self):
self.arquivos.clear()
for item in os.listdir(self.pasta_origem):
p = os.path.join(self.pasta_origem, item)
if os.path.isfile(p):
self.arquivos.append(ArquivoInfo(p, item, os.path.getsize(p)))
return self.arquivos
degrau_2_primeira_classe/test_degrau_2.py):"""
Testes Unitários para o Degrau 2 do Projeto 10 (Primeira Classe POO)
"""
import os
import shutil
import unittest
from main import ArquivoInfo, GerenciadorBackup
PASTA_ORIGEM_TESTE = "teste_poo_origem"
PASTA_DESTINO_TESTE = "teste_poo_destino"
class TestDegrau2Backup(unittest.TestCase):
def setUp(self):
os.makedirs(PASTA_ORIGEM_TESTE, exist_ok=True)
if os.path.exists(PASTA_DESTINO_TESTE):
shutil.rmtree(PASTA_DESTINO_TESTE)
with open(os.path.join(PASTA_ORIGEM_TESTE, "doc1.txt"), "w") as f:
f.write("A" * 2048) # 2 KB
with open(os.path.join(PASTA_ORIGEM_TESTE, "doc2.txt"), "w") as f:
f.write("B" * 512) # 512 Bytes
def tearDown(self):
if os.path.exists(PASTA_ORIGEM_TESTE):
shutil.rmtree(PASTA_ORIGEM_TESTE)
if os.path.exists(PASTA_DESTINO_TESTE):
shutil.rmtree(PASTA_DESTINO_TESTE)
def test_classe_arquivo_info(self):
a1 = ArquivoInfo("/caminho/doc.txt", "doc.txt", 1024)
self.assertEqual(a1.tamanho_formatado(), "1.00 KB")
a2 = ArquivoInfo("/caminho/foto.jpg", "foto.jpg", 2 * 1024 * 1024)
self.assertEqual(a2.tamanho_formatado(), "2.00 MB")
def test_gerenciador_backup_execucao(self):
ger = GerenciadorBackup(PASTA_ORIGEM_TESTE, PASTA_DESTINO_TESTE)
arqs = ger.escanear()
self.assertEqual(len(arqs), 2)
self.assertEqual(ger.volume_total_bytes(), 2560)
copiados, total_b = ger.executar_backup()
self.assertEqual(copiados, 2)
self.assertEqual(total_b, 2560)
self.assertTrue(os.path.exists(os.path.join(PASTA_DESTINO_TESTE, "doc1.txt")))
if __name__ == "__main__":
unittest.main()
No terceiro degrau, cada execução de backup gera um cabeçalho em execucoes_backup e múltiplos itens detalhados em arquivos_backup.
degrau_3_sqlite/main.py):import sqlite3
def inicializar_tabelas(caminho="backup.db"):
conn = sqlite3.connect(caminho)
conn.execute("PRAGMA foreign_keys = ON;")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS execucoes_backup (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pasta_origem TEXT NOT NULL,
pasta_destino TEXT NOT NULL,
total_arquivos INTEGER NOT NULL,
total_bytes INTEGER NOT NULL,
status TEXT DEFAULT 'SUCESSO',
data_execucao TEXT DEFAULT (datetime('now', 'localtime'))
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS arquivos_backup (
id INTEGER PRIMARY KEY AUTOINCREMENT,
backup_id INTEGER NOT NULL,
nome_arquivo TEXT NOT NULL,
tamanho_bytes INTEGER NOT NULL,
FOREIGN KEY (backup_id) REFERENCES execucoes_backup(id) ON DELETE CASCADE
)
""")
conn.commit()
conn.close()
degrau_3_sqlite/test_degrau_3.py):"""
Testes Unitários para o Degrau 3 do Projeto 10 (SQLite3 Relacional com Auditoria)
"""
import os
import shutil
import unittest
from main import (
executar_backup_e_gravar,
inicializar_tabelas,
listar_historico_backups,
obter_metricas_gerais
)
PASTA_ORIGEM_TESTE = "teste_sql_origem"
PASTA_DESTINO_TESTE = "teste_sql_destino"
BANCO_TESTE = "teste_backup_degrau3.db"
class TestDegrau3SQLite(unittest.TestCase):
def setUp(self):
os.makedirs(PASTA_ORIGEM_TESTE, exist_ok=True)
if os.path.exists(PASTA_DESTINO_TESTE):
shutil.rmtree(PASTA_DESTINO_TESTE)
if os.path.exists(BANCO_TESTE):
os.remove(BANCO_TESTE)
inicializar_tabelas(caminho=BANCO_TESTE)
# Arquivos de teste
with open(os.path.join(PASTA_ORIGEM_TESTE, "balanco_2026.xlsx"), "w") as f:
f.write("A" * 4096)
with open(os.path.join(PASTA_ORIGEM_TESTE, "folha_pagamento.pdf"), "w") as f:
f.write("B" * 2048)
def tearDown(self):
if os.path.exists(PASTA_ORIGEM_TESTE):
shutil.rmtree(PASTA_ORIGEM_TESTE)
if os.path.exists(PASTA_DESTINO_TESTE):
shutil.rmtree(PASTA_DESTINO_TESTE)
if os.path.exists(BANCO_TESTE):
os.remove(BANCO_TESTE)
def test_execucao_e_auditoria_sqlite(self):
b_id = executar_backup_e_gravar(PASTA_ORIGEM_TESTE, PASTA_DESTINO_TESTE, caminho_db=BANCO_TESTE)
self.assertIsNotNone(b_id)
# Métricas
m = obter_metricas_gerais(caminho_db=BANCO_TESTE)
self.assertEqual(m["total_backups"], 1)
self.assertEqual(m["total_arquivos"], 2)
self.assertEqual(m["volume_geral_bytes"], 6144)
# Histórico
hist = listar_historico_backups(caminho_db=BANCO_TESTE)
self.assertEqual(len(hist), 1)
self.assertEqual(hist[0][3], 2) # total_arquivos
if __name__ == "__main__":
unittest.main()
No quarto degrau, o aluno cria uma aplicação desktop completa com botões para navegação no Windows Explorer e barra de progresso durante a transferência.
degrau_4_interface_customtkinter/main.py):import tkinter as tk
from tkinter import filedialog, messagebox, ttk
class AppBackup:
def __init__(self, root):
self.root = root
self.root.title("💾 Catalogador de Arquivos & Backups (Degrau 4)")
self.root.geometry("760x650")
self.criar_widgets()
self.recarregar_dados()
main.py)O arquivo main.py na raiz do projeto funciona como um painel de controle: permite executar qualquer um dos 4 degraus (ou toda a suíte de testes) a partir de um único menu no terminal, sem precisar navegar manualmente entre as pastas.
main.py):"""
Hub Central / Launcher — Miniprojeto 10: Catalogador de Arquivos & Gerenciador de Backups
Permite executar de forma autoguiada qualquer um dos 4 degraus pedagógicos ou rodar a suíte de testes.
"""
import os
import subprocess
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def rodar_script(caminho_relativo):
caminho_absoluto = os.path.join(BASE_DIR, caminho_relativo)
diretorio = os.path.dirname(caminho_absoluto)
print(f"\n🚀 Executando: {caminho_relativo}...\n")
try:
subprocess.run([sys.executable, caminho_absoluto], cwd=diretorio, check=True)
except KeyboardInterrupt:
print("\n\n⏹️ Execucao interrompida pelo usuario.")
except Exception as e:
print(f"\n❌ Erro durante a execucao: {e}")
def rodar_todos_os_testes():
print("\n🧪 ========================================================")
print(" RODANDO A SUÍTE DE TESTES DOS DEGRAUS 1, 2 E 3")
print("========================================================\n")
testes = [
("Degrau 1 (Imperativo & Arquivo TXT)", "degrau_1_imperativo_txt/test_degrau_1.py"),
("Degrau 2 (Primeira Classe POO)", "degrau_2_primeira_classe/test_degrau_2.py"),
("Degrau 3 (Banco SQLite3 Relacional)", "degrau_3_sqlite/test_degrau_3.py"),
]
for nome, arq in testes:
print(f"▶️ Testando {nome}...")
caminho_abs = os.path.join(BASE_DIR, arq)
diretorio = os.path.dirname(caminho_abs)
res = subprocess.run([sys.executable, caminho_abs], cwd=diretorio)
if res.returncode == 0:
print(f"✅ {nome}: PASSOU!\n")
else:
print(f"❌ {nome}: FALHOU!\n")
def menu_hub():
while True:
print("\n========================================================")
print("💾 MINIPROJETO 10: CATALOGADOR & BACKUP DE ARQUIVOS (HUB)")
print("========================================================")
print("1. 🪜 Executar Degrau 1 (Imperativo & Log .txt com os/shutil)")
print("2. 🪜 Executar Degrau 2 (Primeira Classe POO: ArquivoInfo e GerenciadorBackup)")
print("3. 🪜 Executar Degrau 3 (Banco de Dados SQLite3 com Auditoria)")
print("4. 🪜 Executar Degrau 4 (Interface Desktop Visual com Progressbar)")
print("5. 🧪 Rodar Todos os Testes Automatizados (Degraus 1, 2 e 3)")
print("6. 🚪 Sair")
print("========================================================")
opcao = input("Escolha o degrau que deseja executar (1-6): ").strip()
if opcao == "1":
rodar_script("degrau_1_imperativo_txt/main.py")
elif opcao == "2":
rodar_script("degrau_2_primeira_classe/main.py")
elif opcao == "3":
rodar_script("degrau_3_sqlite/main.py")
elif opcao == "4":
rodar_script("degrau_4_interface_customtkinter/main.py")
elif opcao == "5":
rodar_todos_os_testes()
elif opcao == "6":
print("👋 Encerrando o launcher. Parabéns por concluir os 10 miniprojetos!")
break
else:
print("❌ Opcao invalida. Digite de 1 a 6.")
if __name__ == "__main__":
menu_hub()
# Executar a suíte de testes do Miniprojeto 10:
python -c "import subprocess, sys; [subprocess.run([sys.executable, p], check=True) for p in ['catalogo_projetos/sistemas_python_intro_10_backup_arquivos/degrau_1_imperativo_txt/test_degrau_1.py', 'catalogo_projetos/sistemas_python_intro_10_backup_arquivos/degrau_2_primeira_classe/test_degrau_2.py', 'catalogo_projetos/sistemas_python_intro_10_backup_arquivos/degrau_3_sqlite/test_degrau_3.py']]"
# Ou, de forma ainda mais simples, use o Hub Central (opção 5 do menu):
python main.py
Resultado obtido:
Ran 1 test in 0.021s OK (Degrau 1)
Ran 2 tests in 0.014s OK (Degrau 2)
Ran 1 test in 0.089s OK (Degrau 3)
A imagem abaixo ilustra a esteira evolutiva dos 4 degraus didáticos do miniprojeto:
flowchart TD
subgraph S1["🪜 Degrau 1: Script Imperativo"]
A1["Menu Terminal while"] --> A2["Arquivo de Texto/JSON"]
end
subgraph S2["🪜 Degrau 2: Orientação a Objetos"]
B1["Modelo de Domínio POO"] --> B2["Encapsulamento de Regras"]
end
subgraph S3["🪜 Degrau 3: Banco Relacional"]
C1["SQLite3 Local"] --> C2["Transações ACID & Agregações"]
end
subgraph S4["🪜 Degrau 4: Interface Visual"]
D1["Aplicação Desktop / Web"] --> D2["UX Interativa Final"]
end
S1 --> S2
S2 --> S3
S3 --> S4
style S1 fill:#fffde7,stroke:#fbc02d,stroke-width:1.5px
style S2 fill:#e3f2fd,stroke:#1976d2,stroke-width:1.5px
style S3 fill:#e8f5e9,stroke:#388e3c,stroke-width:1.5px
style S4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1.5px
| ID | Persona | História de Usuário | Critério de Aceite | Prioridade |
|---|---|---|---|---|
| US-01 | Técnico de Suporte | Desejo escanear pastas e salvar relatórios de arquivos em formato texto | Gravar caminhos, tamanhos e extensões em relatorio_backup.txt | Alta |
| US-02 | Administrador de Redes | Desejo calcular checksum SHA-256 e tamanho através de classe POO | Classe ArquivoSnapshot com métodos de verificação de integridade e hash | Alta |
| US-03 | Auditor de TI | Desejo catálogo relacional no SQLite para detecção de arquivos duplicados | Queries com GROUP BY hash HAVING COUNT(*) > 1 para identificar clones | Alta |
| US-04 | Operador de Backup | Desejo interface desktop CustomTkinter com seletor de pastas e métricas | Janela moderna com barra de progresso, gráficos de espaço e árvore de arquivos | Alta |
| US-05 | Auditor de Segurança | Desejo testes assegurando detecção de arquivos corrompidos ou alterados | Testes unitários simulando alterações de conteúdo e checagem de hash | Média |
| ID | Descrição do Requisito | Criticidade |
|---|---|---|
| RF-01 | Varredura recursiva de diretórios locais e persistência de metadados em texto | Alta |
| RF-02 | Cálculo de hash criptográfico SHA-256 e detecção de duplicidades em POO | Alta |
| RF-03 | Banco relacional SQLite com indexação por hash, tamanho e data de modificação | Alta |
| RF-04 | Interface CustomTkinter com explorador visual de diretórios e painel de análise | Alta |
| RF-05 | Launcher integrador e testes automatizados de hashing e queries de deduplicação | Média |
| ID | Especificação Arquitetural | Criticidade |
|---|---|---|
| RNF-01 | Processamento eficiente de arquivos em buffers de 64KB sem estouro de RAM | Alta |
| RNF-02 | Compatibilidade de leitura de caminhos e separadores no Windows, Linux e Mac | Alta |
| RNF-03 | Tratamento de erros de permissão de acesso a pastas e arquivos protegidos | Média |
| RNF-04 | Interface desktop responsiva sem travamento da thread principal (GUI) | Alta |
| RNF-05 | Estruturação didática e modular nos 4 degraus instrucionais | Alta |
| User Story | Requisito Funcional | Requisito Não-Funcional | Módulo / Artefato de Código | Status de Homologação |
|---|---|---|---|---|
| US-01 | RF-01 |
RNF-01, RNF-02 |
degrau_1_imperativo_txt/main.py |
Testado |
| US-02 | RF-02 |
RNF-01, RNF-05 |
degrau_2_primeira_classe/main.py |
Testado |
| US-03 | RF-03 |
RNF-01, RNF-03 |
degrau_3_sqlite/main.py |
Testado |
| US-04 | RF-04 |
RNF-02, RNF-04 |
degrau_4_interface_customtkinter/main.py |
Testado |
| US-05 | RF-05 |
RNF-01, RNF-05 |
main.py & test_degrau_*.py |
Testado |
| Checkpoint | Escopo da Validação | Artefato Verificado | Status |
|---|---|---|---|
| CP-01 | Script Imperativo & Persistência Inicial | degrau_1_* |
PASS ✅ |
| CP-02 | Modelagem Orientada a Objetos | degrau_2_* |
PASS ✅ |
| CP-03 | Integridade Relacional no SQLite | degrau_3_* |
PASS ✅ |
| CP-04 | Interface Visual & Usabilidade | degrau_4_* |
PASS ✅ |
| CP-05 | Hub Launcher Central & Suíte de Testes | main.py + test_*.py |
PASS ✅ |