📅 Data de Geração: 2025-12-02 12:00:00
🤖 Gerado por: GitHub Copilot - Agent AI (Claude Opus 4.5)
📌 Versão do Documento: 2.0
🐍 Linguagem: Python 3.12
🎯 Público-Alvo: Estagiários e desenvolvedores iniciantes
- Introdução
- Diretórios Excluídos da Análise
- Árvore de Diretórios
- Arquivos da Raiz
- Pasta init/
- Pasta src/
- Pasta src/controllers/
- Pasta src/controllers/interfaces/
- Pasta src/models/
- Pasta src/models/sqlite/entities/
- Pasta src/models/sqlite/interfaces/
- Pasta src/models/sqlite/repositories/
- Pasta src/models/sqlite/settings/
- Comandos Úteis
- Checklist de Entendimento
- Perguntas Sugeridas
- Histórico de Geração
Este projeto é um exemplo didático de arquitetura de software em Python que implementa um sistema de gerenciamento de pets e pessoas usando:
- SQLAlchemy como ORM (Object-Relational Mapping)
- SQLite como banco de dados
- Padrão Repository para abstração de acesso a dados
- Interfaces (ABC) para contratos entre camadas
- Pytest para testes unitários
Analogia Pedagógica: Imagine o projeto como um restaurante:
controllers/= garçons (recebem pedidos e entregam respostas)repositories/= cozinha (prepara/busca os dados)entities/= cardápio (define a estrutura dos "pratos"/dados)interfaces/= manual de operações (regras que todos devem seguir)
Os seguintes diretórios foram excluídos desta documentação:
| Diretório | Motivo |
|---|---|
__pycache__/ |
Bytecode compilado do Python (gerado automaticamente) |
venv/ |
Ambiente virtual Python |
.venv/ |
Ambiente virtual Python (alternativo) |
.git/ |
Controle de versão Git |
.pytest_cache/ |
Cache do pytest |
.vscode/ |
Configurações da IDE |
arquitetura_de_software/
├── 📄 ex_pylint.py # Exemplo básico de Python
├── 📄 PROJECT_DOCS.md # Esta documentação
├── 📄 requirements.txt # Dependências do projeto
│
├── 📁 init/
│ └── 📄 schema.sql # Script de criação do banco
│
└── 📁 src/ # Código-fonte principal
├── 📄 __init__.py
│
├── 📁 controllers/ # Camada de controle
│ ├── 📄 __init__.py
│ ├── 📄 person_creator_controller.py
│ ├── 📄 person_creator_controller_test.py
│ ├── 📄 person_finder_controller.py
│ ├── 📄 person_finder_controller_test.py
│ ├── 📄 pet_deleter_controller.py
│ ├── 📄 pet_deleter_controller_test.py
│ ├── 📄 pet_lister_controller.py
│ ├── 📄 pet_lister_controller_test.py
│ ├── 📄 pets_creator_controller.py
│ │
│ └── 📁 interfaces/ # Contratos dos controllers
│ ├── 📄 __init__.py
│ ├── 📄 person_creator_controller.py
│ ├── 📄 person_finder_controller.py
│ ├── 📄 pet_deleter_controller.py
│ └── 📄 pet_lister_controller.py
│
└── 📁 models/ # Camada de dados
├── 📄 __init__.py
│
└── 📁 sqlite/ # Implementação SQLite
├── 📄 __init__.py
│
├── 📁 entities/ # Entidades/Tabelas
│ ├── 📄 __init__.py
│ ├── 📄 people.py
│ └── 📄 pets.py
│
├── 📁 interfaces/ # Contratos dos repositories
│ ├── 📄 __init__.py
│ ├── 📄 people_repository.py
│ └── 📄 pets_repository.py
│
├── 📁 repositories/ # Implementação dos repositories
│ ├── 📄 __init__.py
│ ├── 📄 people_repository.py
│ ├── 📄 pets_repository.py
│ ├── 📄 pets_repository_test.py
│ └── 📄 repositories_test.py
│
└── 📁 settings/ # Configurações do banco
├── 📄 __init__.py
├── 📄 base.py
├── 📄 connection.py
└── 📄 connection_test.py
Caminho: ./requirements.txt
Propósito: Lista todas as dependências (bibliotecas) necessárias para o projeto funcionar.
Dependências Principais:
| Pacote | Versão | Função |
|---|---|---|
SQLAlchemy |
2.0.44 | ORM para banco de dados |
pytest |
9.0.1 | Framework de testes |
mock-alchemy |
0.2.6 | Mocks para SQLAlchemy em testes |
pylint |
4.0.2 | Analisador de código |
pre_commit |
4.4.0 | Hooks de pré-commit |
Caminho: ./ex_pylint.py | Linhas: 7
Propósito: Arquivo de exemplo simples para demonstrar boas práticas de Python com Pylint.
# L1-L7 - ./ex_pylint.py
print("Ola mundo") # L1: Imprime uma saudação simples no console
def minha_funcao(): # L4: Define uma função chamada "minha_funcao"
"""Minha função""" # L5: Docstring - documentação obrigatória para funções (boa prática)
print("Estou na minha funcao") # L6: Imprime mensagem quando a função é chamada📝 Notas Didáticas:
- A docstring (linha 5) é obrigatória pelo Pylint para documentar funções
- Este arquivo serve como exemplo mínimo de código que passa na validação do Pylint
Propósito: Contém scripts de inicialização do banco de dados.
Caminho: ./init/schema.sql | Linhas: 23
Propósito: Script SQL que cria as tabelas do banco de dados e insere dados iniciais.
-- L1-L6 - Criação da tabela de pets
CREATE TABLE IF NOT EXISTS 'pets' (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- Chave primária auto-incrementada
name TEXT NOT NULL, -- Nome do pet (obrigatório)
type TEXT NOT NULL -- Tipo do pet: dog, cat, etc. (obrigatório)
);
-- L8-L15 - Criação da tabela de pessoas
CREATE TABLE IF NOT EXISTS 'people' (
id INTEGER PRIMARY KEY AUTOINCREMENT,
first_name TEXT NOT NULL, -- Primeiro nome
last_name TEXT NOT NULL, -- Sobrenome
age INTEGER NOT NULL, -- Idade
pet_id INTEGER NOT NULL, -- FK para a tabela pets
FOREIGN KEY (pet_id) REFERENCES pets(id) -- Relacionamento: cada pessoa TEM UM pet
);
-- L17-L23 - Dados iniciais de exemplo
INSERT INTO pets(name, type) VALUES
("cobra","snake"), ("cao","dog"), ("gato","cat"),
("jorgin","hamster"), ("burro","donkey"),
("shrek","ogro"), ("belinha","dog");📝 Notas Didáticas:
PRIMARY KEY AUTOINCREMENT: o banco gera IDs únicos automaticamenteFOREIGN KEY: estabelece relacionamento entre tabelas (uma pessoa pertence a um pet)NOT NULL: campo obrigatório, não pode ficar vazio
Propósito: Diretório raiz do código-fonte. Contém toda a lógica da aplicação organizada em camadas.
Arquivos:
__init__.py- Arquivo vazio que marca o diretório como um pacote Python
📝 Nota: O arquivo __init__.py vazio é necessário para que o Python reconheça a pasta como um módulo importável.
Propósito: Camada de controle (ou "apresentação"). Os controllers:
- Recebem dados de entrada
- Validam as informações
- Chamam os repositories para operações no banco
- Formatam a resposta de saída
Analogia: São como garçons - recebem o pedido do cliente, verificam se está correto, passam para a cozinha (repository) e entregam o prato pronto formatado.
Caminho: ./src/controllers/person_creator_controller.py | Linhas: 36
Propósito: Controller responsável por criar novas pessoas no sistema.
Dependências/Imports:
from typing import Dict # Tipagem para dicionários
import re # Expressões regulares para validação
from src.models.sqlite.interfaces.people_repository import PeopleRepositoryInterface
from .interfaces.person_creator_controller import PersonCreatorControllerInterfaceCódigo completo com explicações linha-a-linha:
# L6-L9 - Definição da classe
class PersonCreatorController(PersonCreatorControllerInterface):
# Herda da interface, OBRIGANDO a implementar o método "create"
def __init__(self, people_repository: PeopleRepositoryInterface) -> None:
# Injeção de Dependência: recebe o repository como parâmetro
# Isso permite trocar o repository real por um mock nos testes
self.__people_repository = people_repository
# O prefixo __ torna o atributo "privado" (encapsulamento)# L10-L19 - Método principal create()
def create(self, person_info: Dict) -> Dict:
# L11-L14: Extrai os dados do dicionário recebido
first_name = person_info["first_name"]
last_name = person_info["last_name"]
age = person_info["age"]
pet_id = person_info["pet_id"]
# L16: Valida se nome/sobrenome contêm apenas letras
self.__validate_first_and_last_name(first_name, last_name)
# L17: Insere no banco de dados via repository
self.__insert_person_in_db(first_name, last_name, age, pet_id)
# L18-L19: Formata e retorna a resposta padronizada
formated_response = self.__format_response(person_info)
return formated_response# L21-L28 - Validação de nome com Regex
def __validate_first_and_last_name(self, first_name: str, last_name: str) -> None:
# Regex: [^a-zA-Z] encontra qualquer caractere que NÃO seja letra
non_valid_caracteres = re.compile(r"[^a-zA-Z]")
# Se encontrar caractere inválido em qualquer um dos nomes...
if non_valid_caracteres.search(first_name) or non_valid_caracteres.search(last_name):
raise Exception("Nome da pessoa inválido")
# Lança exceção que interrompe a execução# L30-L33 - Inserção no banco (delega para o repository)
def __insert_person_in_db(self, first_name: str, last_name: str, age: int, pet_id: int) -> None:
self.__people_repository.insert_person(first_name, last_name, age, pet_id)
# O controller NÃO sabe como inserir no banco - apenas chama o repository# L35-L36 - Formatação da resposta
def __format_response(self, person_info: Dict) -> Dict:
return {"data": {"type": "Person", "count": 1, "attributes": person_info}}
# Retorna um dicionário padronizado com:
# - type: tipo do recurso
# - count: quantidade de itens
# - attributes: os dados em si- A validação de nome não aceita acentos (
é,ã, etc.) nem espaços. Nomes como "José" ou "Maria Clara" seriam rejeitados.
Caminho: ./src/controllers/person_creator_controller_test.py | Linhas: 35
Propósito: Testes unitários para o PersonCreatorController.
# L1-L3 - Imports
import pytest # Framework de testes
from .person_creator_controller import PersonCreatorController
# L5-L8 - Mock do Repository
class MockPeopleRepository:
"""Objeto falso que simula o repository real"""
def insert_person(self, first_name: str, last_name: str, age: int, pet_id: int):
pass # Não faz nada - apenas simula a assinatura do método# L11-L23 - Teste de sucesso
def test_create():
# Arrange (Preparação)
person_infor = {
"first_name": "Fulano",
"last_name": "deTal",
"age": 30,
"pet_id": 123,
}
# Act (Execução)
controller = PersonCreatorController(MockPeopleRepository())
response = controller.create(person_infor)
# Assert (Verificação)
assert response["data"]["type"] == "Person"
assert response["data"]["count"] == 1
assert response["data"]["attributes"] == person_infor# L26-L35 - Teste de erro (nome inválido)
def test_create_error():
person_infor = {
"first_name": "Fulano123", # Nome com números = inválido!
"last_name": "deTal",
"age": 30,
"pet_id": 123,
}
controller = PersonCreatorController(MockPeopleRepository())
# pytest.raises: espera que uma exceção seja lançada
with pytest.raises(Exception):
controller.create(person_infor)📝 Notas Didáticas:
- Mock: objeto "falso" que simula comportamento real
- Padrão AAA: Arrange (preparar), Act (executar), Assert (verificar)
- O mock permite testar o controller SEM precisar de banco de dados real
Caminho: ./src/controllers/person_finder_controller.py | Linhas: 32
Propósito: Controller responsável por buscar uma pessoa pelo ID.
# L1-L4 - Imports
from typing import Dict
from src.models.sqlite.interfaces.people_repository import PeopleRepositoryInterface
from src.models.sqlite.entities.people import People
from .interfaces.person_finder_controller import PersonFinderControllerInterface# L6-L9 - Classe com injeção de dependência
class PersonFinderController(PersonFinderControllerInterface):
def __init__(self, people_repository: PeopleRepositoryInterface) -> None:
self.__people_repository = people_repository# L11-L14 - Método principal find()
def find(self, person_id: int) -> Dict:
person = self.__find_person_in_db(person_id) # Busca no banco
response = self.__format_response(person) # Formata resposta
return response# L16-L20 - Busca no banco com tratamento de erro
def __find_person_in_db(self, person_id: int) -> People:
person = self.__people_repository.get_person(person_id)
if not person:
raise Exception("Pessoa não encontrada!") # Erro se não existir
return person# L22-L32 - Formatação da resposta
def __format_response(self, person: People) -> Dict:
return {
"data": {
"type": "Person",
"count": 1,
"attributes": {
"first_name": person.first_name,
"last_name": person.last_name,
"pet_name": person.pet_name, # Nome do pet (via JOIN)
"pet_type": person.pet_type # Tipo do pet (via JOIN)
}
}
}Caminho: ./src/controllers/person_finder_controller_test.py | Linhas: 35
Propósito: Testes unitários para o PersonFinderController.
# L1-L2 - Desabilita aviso do Pylint sobre argumentos não usados
#pylint: disable = unused-argument
from .person_finder_controller import PersonFinderController
# L5-L11 - Mock que simula uma pessoa retornada do banco
class MockPerson:
def __init__(self, first_name, last_name, pet_name, pet_type) -> None:
self.first_name = first_name
self.last_name = last_name
self.pet_name = pet_name
self.pet_type = pet_type
# L14-L19 - Mock do repository
class MockPeopleRepository:
def get_person(self, person_id: int):
# Sempre retorna a mesma pessoa fictícia
return MockPerson(
first_name="John", last_name="Doe",
pet_name="Fluffy", pet_type="cat"
)
# L22-L35 - Teste de busca
def test_find():
controller = PersonFinderController(MockPeopleRepository())
response = controller.find(123) # ID qualquer
expected_response = {
"data": {
"type": "Person",
"count": 1,
"attributes": {
"first_name": "John",
"last_name": "Doe",
"pet_name": "Fluffy",
"pet_type": "cat",
},
}
}
assert response == expected_responseCaminho: ./src/controllers/pet_lister_controller.py | Linhas: 31
Propósito: Controller responsável por listar todos os pets.
# L1-L4 - Imports
from typing import Dict, List
from src.models.sqlite.interfaces.pets_repository import PetsRepositoryInterface
from src.models.sqlite.entities.pets import PetsTable
from .interfaces.pet_lister_controller import PetListerControllerInterface# L6-L9 - Classe
class PetListerController(PetListerControllerInterface):
def __init__(self, pet_repository: PetsRepositoryInterface) -> None:
self.__pet_repository = pet_repository# L11-L14 - Método list()
def list(self) -> Dict:
pets = self.__get_pets_in_db() # Busca todos os pets
response = self.__format_response(pets) # Formata lista
return response# L16-L18 - Busca no banco
def __get_pets_in_db(self) -> List[PetsTable]:
pets = self.__pet_repository.list_pets()
return pets# L20-L31 - Formatação da lista
def __format_response(self, pets: List[PetsTable]) -> Dict:
formatted_pets = []
for pet in pets:
# Extrai apenas nome e id de cada pet
formatted_pets.append({"name": pet.name, "id": pet.id})
return {
"data": {
"type": "Pets",
"count": len(formatted_pets), # Quantidade de pets
"attributes": formatted_pets # Lista de pets
}
}Caminho: ./src/controllers/pet_lister_controller_test.py | Linhas: 30
Propósito: Testes unitários para o PetListerController.
# L1-L2 - Imports
from src.models.sqlite.entities.pets import PetsTable
from .pet_lister_controller import PetListerController
# L4-L10 - Mock que retorna lista de pets
class MockPetsRepository:
def list_pets(self):
return [
PetsTable(name="Fluffy", type="Cat", id=4),
PetsTable(name="Buddy", type="Dog", id=47),
]
# L13-L30 - Teste de listagem
def test_list_pets():
controller = PetListerController(MockPetsRepository())
response = controller.list()
expected_response = {
"data": {
"type": "Pets",
"count": 2,
"attributes": [
{"name": "Fluffy", "id": 4},
{"name": "Buddy", "id": 47}
],
}
}
assert response == expected_responseCaminho: ./src/controllers/pet_deleter_controller.py | Linhas: 11
Propósito: Controller responsável por deletar um pet pelo nome.
# L1-L3 - Imports
from src.models.sqlite.interfaces.pets_repository import PetsRepositoryInterface
from .interfaces.pet_deleter_controller import PetDeleterControllerInterface
# L5-L10 - Classe simples de deleção
class PetDeleterController(PetDeleterControllerInterface):
def __init__(self, pet_repository: PetsRepositoryInterface) -> None:
self.__pet_repository = pet_repository
def delete(self, name: str) -> None:
self.__pet_repository.delete_pet(name)
# Delega a deleção diretamente ao repository- O método chama
delete_pet(name)mas a interface definedelete_pets(name). Pode haver inconsistência de nomes.
Caminho: ./src/controllers/pet_deleter_controller_test.py | Linhas: 10
Propósito: Teste do PetDeleterController usando mocker do pytest.
# L1-L9 - Teste com mocker
from src.controllers.pet_deleter_controller import PetDeleterController
def test_delete_pet(mocker):
mock_repository = mocker.Mock() # Cria mock dinâmico
controller = PetDeleterController(mock_repository)
controller.delete("amiguinho")
# Verifica se delete_pet foi chamado UMA vez com "amiguinho"
mock_repository.delete_pet.assert_called_once_with("amiguinho")📝 Nota: mocker.Mock() cria automaticamente qualquer método chamado nele.
Caminho: ./src/controllers/pets_creator_controller.py | Linhas: 5
Propósito: Controller para criar pets (incompleto).
# L1-L5 - Estrutura básica
from src.models.sqlite.interfaces.pets_repository import PetsRepositoryInterface
class PetsCreatorController:
def __init__(self, pets_repository: PetsRepositoryInterface):
self.__pets_repository = pets_repository- Classe incompleta - não possui método
create()nem implementa interface.
Propósito: Define os contratos (interfaces) que os controllers devem implementar.
Analogia: É como um manual de instruções que diz "todo garçom DEVE saber anotar pedidos". A interface não diz COMO fazer, apenas O QUE deve ser feito.
Caminho: ./src/controllers/interfaces/person_creator_controller.py | Linhas: 12
# L1-L2 - Imports para criar interface
from typing import Dict
from abc import ABC, abstractmethod # ABC = Abstract Base Class
# L5-L9 - Interface abstrata
class PersonCreatorControllerInterface(ABC):
"""Contrato: todo controller de criar pessoa DEVE ter método create()"""
@abstractmethod # Decorador que OBRIGA implementação
def create(self, person_info: Dict) -> Dict:
pass # Não tem implementação - é apenas a assinatura📝 Notas Didáticas:
ABC: classe base abstrata - não pode ser instanciada diretamente@abstractmethod: marca método que DEVE ser implementado pelas classes filhas- Se uma classe herda mas não implementa, Python lança erro
Caminho: ./src/controllers/interfaces/person_finder_controller.py | Linhas: 12
from typing import Dict
from abc import ABC, abstractmethod
class PersonFinderControllerInterface(ABC):
@abstractmethod
def find(self, person_id: int) -> Dict:
passCaminho: ./src/controllers/interfaces/pet_lister_controller.py | Linhas: 11
from typing import Dict, List
from abc import ABC, abstractmethod
class PetListerControllerInterface(ABC):
@abstractmethod
def list(self) -> Dict:
pass- Import
Listnão é utilizado na interface.
Caminho: ./src/controllers/interfaces/pet_deleter_controller.py | Linhas: 9
from abc import ABC, abstractmethod
class PetDeleterControllerInterface(ABC):
@abstractmethod
def delete(self, name: str) -> None:
passPropósito: Camada de dados/modelo. Contém tudo relacionado ao armazenamento e recuperação de dados.
Propósito: Define as entidades (tabelas do banco de dados) usando SQLAlchemy ORM.
Analogia: São os "moldes" que definem a estrutura de cada tabela. Como um formulário que diz quais campos existem.
Caminho: ./src/models/sqlite/entities/people.py | Linhas: 24
# L1-L3 - Imports do SQLAlchemy
from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import relationship
from src.models.sqlite.settings.base import Base # Base declarativa
# L5-L23 - Definição da tabela People
class People(Base):
__tablename__ = "people" # Nome da tabela no banco
# Definição das colunas:
id = Column(Integer, primary_key=True) # PK auto-incrementada
first_name = Column(String, nullable=False) # NOT NULL
last_name = Column(String, nullable=False)
age = Column(Integer, nullable=False)
pet_id = Column(Integer, ForeignKey('pets.id')) # Chave estrangeira
# Relacionamento comentado (não ativo)
# pets = relationship('PetsTable', back_populates='people')
def __repr__(self):
"""Representação textual do objeto (útil para debug)"""
return f"People(first_name={self.first_name}, last_name={self.last_name}, age={self.age})"📝 Notas Didáticas:
Column: define uma coluna da tabelaprimary_key=True: chave primária únicanullable=False: equivalente a NOT NULL no SQLForeignKey('pets.id'): referência à tabela pets__repr__: como o objeto aparece quando printado
Caminho: ./src/models/sqlite/entities/pets.py | Linhas: 17
# L1-L3 - Imports
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import relationship
from src.models.sqlite.settings.base import Base
# L5-L17 - Definição da tabela Pets
class PetsTable(Base):
__tablename__ = "pets" # Nome da tabela
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False) # Nome do pet
type = Column(String, nullable=False) # Tipo: dog, cat, etc.
# Relacionamento comentado
# people = relationship('People', back_populates='pets')
def __repr__(self):
return f"PetsTable(name={self.name}, type={self.type})"Propósito: Define os contratos que os repositories devem seguir.
Caminho: ./src/models/sqlite/interfaces/people_repository.py | Linhas: 14
# L1-L3 - Imports
from abc import ABC, abstractmethod
from src.models.sqlite.entities.people import People
# L5-L14 - Interface do repository de pessoas
class PeopleRepositoryInterface(ABC):
@abstractmethod
def insert_person(self, first_name: str, last_name: str, age: int, pet_id: int) -> None:
"""Insere uma nova pessoa no banco"""
pass
@abstractmethod
def get_person(self, person_id: int) -> People:
"""Busca uma pessoa pelo ID"""
passCaminho: ./src/models/sqlite/interfaces/pets_repository.py | Linhas: 15
# L1-L3 - Imports
from abc import ABC, abstractmethod
from typing import List
from src.models.sqlite.entities.pets import PetsTable
# L6-L15 - Interface do repository de pets
class PetsRepositoryInterface(ABC):
@abstractmethod
def list_pets(self) -> List[PetsTable]:
"""Lista todos os pets"""
pass
@abstractmethod
def delete_pets(self, name: str) -> None:
"""Deleta um pet pelo nome"""
passPropósito: Implementação concreta dos repositories. Contém a lógica real de acesso ao banco de dados.
Analogia: É a "cozinha" do restaurante - onde o trabalho real de preparar/buscar dados acontece.
Caminho: ./src/models/sqlite/repositories/people_repository.py | Linhas: 44
# L1-L4 - Imports
from sqlalchemy.orm.exc import NoResultFound
from src.models.sqlite.entities.people import People
from src.models.sqlite.entities.pets import PetsTable
from src.models.sqlite.interfaces.people_repository import PeopleRepositoryInterface# L6-L9 - Classe do repository
class PeopleRepository(PeopleRepositoryInterface):
def __init__(self, db_connection) -> None:
self.__db_connection = db_connection # Recebe conexão por injeção# L11-L23 - Inserção de pessoa
def insert_person(self, first_name: str, last_name: str, age: int, pet_id: int) -> None:
with self.__db_connection as database:
# "with" garante que a conexão será fechada ao final
try:
person_data = People(
first_name=first_name,
last_name=last_name,
age=age,
pet_id=pet_id
)
database.session.add(person_data) # Adiciona à sessão
database.session.commit() # Salva no banco
except Exception as exception:
database.session.rollback() # Desfaz em caso de erro
raise exception# L25-L44 - Busca de pessoa com JOIN
def get_person(self, person_id: int) -> People:
with self.__db_connection as database:
try:
person = (
database.session
.query(People)
.outerjoin(PetsTable, PetsTable.id == People.id) # LEFT JOIN
.filter(People.id == person_id)
.with_entities( # Seleciona campos específicos
People.first_name,
People.last_name,
PetsTable.name.label("pet_name"), # Alias
PetsTable.type.label("pet_type"),
)
.one() # Retorna exatamente 1 resultado
)
return person
except NoResultFound:
return None # Retorna None se não encontrar- O JOIN usa
PetsTable.id == People.idmas deveria serPetsTable.id == People.pet_id.
Caminho: ./src/models/sqlite/repositories/pets_repository.py | Linhas: 33
# L1-L5 - Imports
from typing import List
from sqlalchemy.orm.exc import NoResultFound
from src.models.sqlite.entities.pets import PetsTable
from src.models.sqlite.interfaces.pets_repository import PetsRepositoryInterface# L7-L10 - Classe
class PetsRepository(PetsRepositoryInterface):
def __init__(self, db_connection) -> None:
self.__db_connection = db_connection# L12-L19 - Listagem de pets
def list_pets(self) -> List[PetsTable]:
with self.__db_connection as database:
try:
pets = database.session.query(PetsTable).all() # SELECT * FROM pets
return pets
except NoResultFound:
return [] # Lista vazia se não houver pets# L21-L33 - Deleção de pet
def delete_pets(self, name: str) -> None:
with self.__db_connection as database:
try:
(
database.session
.query(PetsTable)
.filter(PetsTable.name == name) # WHERE name = ?
.delete() # DELETE
)
database.session.commit()
except Exception as exception:
database.session.rollback()
raise exceptionCaminho: ./src/models/sqlite/repositories/pets_repository_test.py | Linhas: 80
Propósito: Testes unitários do PetsRepository usando mock_alchemy.
# L1-L6 - Imports
from unittest import mock
import pytest
from mock_alchemy.mocking import UnifiedAlchemyMagicMock # Mock especial para SQLAlchemy
from sqlalchemy.orm.exc import NoResultFound
from src.models.sqlite.entities.pets import PetsTable
from .pets_repository import PetsRepository# L9-L25 - Mock de conexão com dados predefinidos
class MockConnection:
def __init__(self) -> None:
self.session = UnifiedAlchemyMagicMock(
data=[
(
[mock.call.query(PetsTable)], # Quando chamar query(PetsTable)...
[ # ...retornar esses dados
PetsTable(name="dog", type="dog"),
PetsTable(name="cat", type="cat"),
],
)
]
)
def __enter__(self): # Para funcionar com "with"
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass# L28-L41 - Mock que simula erro
class MockConnectionNoResult:
def __init__(self) -> None:
self.session = UnifiedAlchemyMagicMock()
self.session.query.side_effect = self.__raise_no_result_found
def __raise_no_result_found(self, *args, **kwargs):
raise NoResultFound("No result found")
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass# L44-L54 - Teste de listagem
def test_list_pets():
mock_connection = MockConnection()
repo = PetsRepository(mock_connection)
response = repo.list_pets()
# Verifica se os métodos corretos foram chamados
mock_connection.session.query.assert_called_once_with(PetsTable)
mock_connection.session.all.assert_called_once()
mock_connection.session.filter.assert_not_called()
assert response[0].name == "dog"Caminho: ./src/models/sqlite/repositories/repositories_test.py | Linhas: 38
Propósito: Testes de integração com banco real (marcados como skip).
# L1-L5 - Imports
import pytest
from src.models.sqlite.settings.connection import db_connecition_handler
from .pets_repository import PetsRepository
from .people_repository import PeopleRepository# L9-L12 - Teste de listagem (skip por padrão)
@pytest.mark.skip(reason="interacao com o banco") # Pula nos testes normais
def test_list_pets():
repo = PetsRepository(db_connecition_handler)
response = repo.list_pets()
print(response)📝 Nota: Estes testes são "skipped" por padrão pois dependem de banco real. Remova o decorador para executar manualmente.
Propósito: Configurações de conexão e base do SQLAlchemy.
Caminho: ./src/models/sqlite/settings/base.py | Linhas: 5
# L1-L4 - Criação da Base declarativa
# from sqlalchemy.ext.declarative import declarative_base (old) # Forma antiga
from sqlalchemy.orm import declarative_base # Forma nova (SQLAlchemy 2.0)
Base = declarative_base()
# Base é a classe-mãe de todas as entidades
# Permite que o SQLAlchemy rastreie as tabelas automaticamenteCaminho: ./src/models/sqlite/settings/connection.py | Linhas: 26
# L1-L2 - Imports
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# L5-L26 - Classe de conexão
class DBConnectionHandler:
def __init__(self) -> None:
self.__connection_string = "sqlite:///storage.db" # Arquivo SQLite
self.__engine = None # Motor de conexão
self.session = None # Sessão atual
def connect_to_db(self):
"""Cria o motor de conexão com o banco"""
self.__engine = create_engine(self.__connection_string)
def get_engine(self):
"""Retorna o motor de conexão"""
return self.__engine
def __enter__(self):
"""Chamado ao entrar no "with" - cria sessão"""
session_maker = sessionmaker()
self.session = session_maker(bind=self.__engine)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Chamado ao sair do "with" - fecha sessão"""
self.session.close()
# Instância global (singleton)
db_connecition_handler = DBConnectionHandler()📝 Notas Didáticas:
- Context Manager (
__enter__/__exit__): permite usarwith db_connection as db: - Singleton:
db_connecition_handleré uma única instância compartilhada - O arquivo do banco (
storage.db) é criado na raiz do projeto
- Typo no nome:
db_connecition_handlerdeveria serdb_connection_handler.
Caminho: ./src/models/sqlite/settings/connection_test.py | Linhas: 13
# L1-L3 - Imports
import pytest
from sqlalchemy.engine import Engine
from .connection import db_connecition_handler
# L7-L13 - Teste de conexão
def test_connect_to_db():
# Antes de conectar, engine deve ser None
assert db_connecition_handler.get_engine() is None
db_connecition_handler.connect_to_db()
db_engine = db_connecition_handler.get_engine()
# Após conectar, deve ser uma instância de Engine
assert db_engine is not None
assert isinstance(db_engine, Engine)# 1. Criar ambiente virtual
python -m venv venv
# 2. Ativar ambiente (Windows PowerShell)
.\venv\Scripts\Activate.ps1
# 3. Instalar dependências
pip install -r requirements.txt# Rodar todos os testes
pytest
# Rodar com detalhes
pytest -s -v
# Rodar arquivo específico
pytest src/controllers/person_creator_controller_test.py -v
# Rodar teste específico
pytest -k "test_create" -v# Via SQLite CLI
sqlite3 storage.db < init/schema.sqlpylint src/# Executar como módulo (recomendado)
python -m src.models.sqlite.repositories.pets_repositoryApós ler esta documentação, o estagiário deve ser capaz de responder:
- O que é o padrão Repository e por que ele é usado?
- Qual a diferença entre uma Interface (ABC) e uma Implementação?
- O que é Injeção de Dependência e por que é útil para testes?
- Como o SQLAlchemy mapeia classes Python para tabelas SQL?
- O que é um Context Manager (
with) e como funciona? - Por que usamos Mocks nos testes unitários?
- Como a estrutura de pastas separa responsabilidades?
- O que acontece quando um método
@abstractmethodnão é implementado?
Perguntas que um estagiário pode fazer para aprofundar o entendimento:
- "Como adiciono uma nova entidade (tabela) ao projeto?"
- "Como faço para rodar apenas os testes de integração com banco real?"
- "Por que os relacionamentos entre People e Pets estão comentados?"
- "Como funcionaria se eu quisesse trocar SQLite por PostgreSQL?"
- "O que é o
UnifiedAlchemyMagicMocke por que não usarMock()normal?" - "Como adiciono validação de idade mínima no
PersonCreatorController?"
| Versão | Data | Observações |
|---|---|---|
| 1.0 | 2025-11-27 | Versão inicial |
| 2.0 | 2025-12-02 | Atualização completa com explicações linha-a-linha |
📝 Observações Finais:
- Typo identificado:
db_connecition_handler→ deveria serdb_connection_handler - Código incompleto:
PetsCreatorControllernão possui implementação - JOIN incorreto: Em
people_repository.py, o JOIN usaPetsTable.id == People.idmas deveria serPetsTable.id == People.pet_id - Validação limitada: Validação de nome não suporta acentos ou espaços
- Imports não utilizados:
Listimportado mas não usado em algumas interfaces
Documento gerado automaticamente por GitHub Copilot - Agent AI