Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/matheus #69

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
__pycache__
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt requirements.txt

RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
55 changes: 31 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,44 @@
![WATTIO](http://wattio.com.br/web/image/1204-212f47c3/Logo%20Wattio.png)
# FastAPI com MySQL

#### Descrição
Este é um projeto de exemplo de uma API com FastAPI e persistência em um banco de dados MySQL.

O desafio consiste em implementar um CRUD de filmes, utilizando [python](https://www.python.org/ "python") integrando com uma API REST e uma possível persistência de dados.
## Inicialização com Docker

Rotas da API:
Para inicializar o projeto com docker, execute os comandos abaixo na raiz do projeto:
```bash
docker compose build
docker compose up
```
Após 15 segundos, a aplicação deverá estar rodando.

- `/filmes` - [GET] deve retornar todos os filmes cadastrados.
- `/filmes` - [POST] deve cadastrar um novo filme.
- `/filmes/{id}` - [GET] deve retornar o filme com ID especificado.
## Estrutura do Projeto

O Objetivo é te desafiar e reconhecer seu esforço para aprender e se adaptar. Qualquer código enviado, ficaremos muito felizes e avaliaremos com toda atenção!
- `main.py`: Ponto de entrada da aplicação FastAPI.
- `controllers/`: Contém os controladores da API.
- `models/`: Define os modelos de dados e interfaces de repositório.
- `services/`: Contém serviços auxiliares, como a conexão com o banco de dados.
- `utils/`: Inclui utilitários, como a configuração do banco de dados.
- `docker-compose.yml`: Arquivo de configuração para o Docker Compose.
- `Dockerfile`: Define como a imagem Docker é construída.

#### Sugestão de Ferramentas
Não é obrigatório utilizar todas as as tecnologias sugeridas, mas será um diferencial =]
## Variáveis de Ambiente

- Orientação a objetos (utilizar objetos, classes para manipular os filmes)
- [FastAPI](https://fastapi.tiangolo.com/) (API com documentação auto gerada)
- [Docker](https://www.docker.com/) / [Docker-compose](https://docs.docker.com/compose/install/) (Aplicação deverá ficar em um container docker, e o start deverá seer com o comando ``` docker-compose up ```
- Integração com banco de dados (persistir as informações em json (iniciante) /[SqLite](https://www.sqlite.org/index.html) / [SQLAlchemy](https://fastapi.tiangolo.com/tutorial/sql-databases/#sql-relational-databases) / outros DB)
Certifique-se de definir as variáveis de ambiente necessárias no arquivo `.env` antes de executar o build do projeto:

- `MYSQL_USER`: Usuário do banco de dados MySQL. (padrão: `root` (não recomendado em produção, ajustar docker-compose))
- `MYSQL_PASSWORD`: Senha do banco de dados MySQL. (padrão: `root` (não recomendado em produção, ajustar docker-compose))
- `MYSQL_DATABASE`: Nome do banco de dados a ser utilizado. (padrão: `filmesDB`)
- `DB_TYPE`: Tipo de banco de dados (`mysql` é a única implementação atualmente).

#### Como começar?
## Testando a API

- Fork do repositório
- Criar branch com seu nome ``` git checkout -b feature/ana ```
- Faça os commits de suas alterações ``` git commit -m "[ADD] Funcionalidade" ```
- Envie a branch para seu repositório ``` git push origin feature/ana ```
- Navegue até o [Github](https://github.com/), crie seu Pull Request apontando para a branch **```main```**
- Atualize o README.md descrevendo como subir sua aplicação
Após iniciar o projeto, você pode acessar a documentação interativa da API em `http://localhost:8000/docs`.

#### Dúvidas?
## Finalizando o Projeto

Qualquer dúvida / sugestão / melhoria / orientação adicional só enviar email para [email protected]
Para parar e remover os contêineres do Docker, utilize o comando:

```bash
docker compose down
```

Salve!
57 changes: 57 additions & 0 deletions controllers/filmeController.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from typing import List
from fastapi import APIRouter
from models.interfaces.filmeRepoInterface import FilmeRepoInterface
from models.filmeModel import FilmeModel, FilmeResponse

class FilmeController():

def __init__(self, filmeRepo : FilmeRepoInterface):

"""
Construtor da classe FilmeController.

Args:
filmeRepo (FilmeRepoInterface): Interface de repositório de filmes.

Attributes:
filmeRepo (FilmeRepoInterface): Interface de repositório de filmes.
router (FastAPI.Routers): Roteador de endpoints da API.
"""

self.filmeRepo = filmeRepo

self.router = APIRouter(tags=["Filmes"])

self.router.add_api_route("/filmes", self.get_filmes, methods=["GET"], response_model = List[FilmeResponse])
self.router.add_api_route("/filmes", self.post_filme, methods=["POST"])
self.router.add_api_route("/filmes/{id}", self.get_filme_by_id, methods=["GET"], response_model = List[FilmeResponse])

def get_filmes(self):
"""
Retorna todos os filmes

Returns:
List[FilmeResponse]: lista de filmes
"""
return self.filmeRepo.get_filmes()

def post_filme(self, filme: FilmeModel):
"""
Cria um novo filme
"""

return self.filmeRepo.post_filme(filme)

def get_filme_by_id(self, id: int):
"""
Retorna um filme pelo seu id

Args:
id (int): o id do filme a ser retornado

Returns:
List[FilmeResponse]: o filme com o id especificado
"""
return self.filmeRepo.get_filme_by_id(id)


22 changes: 22 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '1.0'

services:
fastapi:
build: .
ports:
- 8000:8000
env_file:
- .env
volumes:
- .:/app
depends_on:
- mysql
entrypoint: sh -c "sleep 10 && uvicorn main:app --host 0.0.0.0 --port 8000"

mysql:
container_name: mysqldb
image: mysql:8.0
ports:
- 3307:3306
environment:
MYSQL_ROOT_PASSWORD: root
18 changes: 18 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from fastapi import FastAPI
from utils.db import mySqlDb
from controllers.filmeController import FilmeController
from services.connectionService import determinar_conexao

app = FastAPI()

repo = determinar_conexao()
item_controller = FilmeController(repo)

app.include_router(item_controller.router)

@app.get("/")
def root():
"""
Rota raiz que redireciona para a documentação da API.
"""
return {"message": "Acesse a documentação da API em /docs"}
10 changes: 10 additions & 0 deletions models/filmeModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pydantic import BaseModel, Field

class FilmeModel(BaseModel):
titulo: str = Field(max_length=255)
diretor: str = Field(max_length=255)
ano_lancamento: int
genero: str = Field(max_length=255)

class FilmeResponse(FilmeModel):
id: int
21 changes: 21 additions & 0 deletions models/interfaces/filmeRepoInterface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import List
from models.filmeModel import FilmeModel

class FilmeRepoInterface:
def get_filmes(self) -> List[FilmeModel]:
"""
Retorna todos os filmes
"""
pass

def post_filme(self, filme: FilmeModel) -> FilmeModel:
"""
Cria um novo filme
"""
pass

def get_filme_by_id(self, id: int) -> FilmeModel:
"""
Retorna um filme pelo seu id
"""
pass
58 changes: 58 additions & 0 deletions models/repos/filmeRepoMySQL.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from typing import List
from models.interfaces.filmeRepoInterface import FilmeRepoInterface
from models.filmeModel import FilmeModel, FilmeResponse

class FilmeRepoMySQL(FilmeRepoInterface):
def __init__(self, conexao_mysql):
"""
Construtor da classe FilmeRepoMySQL

Args:
conexao_mysql (conexao mysql): Conexao com o banco de dados mysql
"""
self.conexao = conexao_mysql

def get_filmes(self) -> List[FilmeResponse]:
"""
Retorna todos os filmes
"""
cursor = self.conexao.cursor()
cursor.execute("SELECT * FROM filmes")
rows = cursor.fetchall()
filmes = []
for row in rows:
filmes.append(FilmeResponse(
id=row[0],
titulo=row[1],
diretor=row[2],
ano_lancamento=row[3],
genero=row[4]
))
return filmes

def post_filme(self, filme: FilmeModel) -> FilmeModel:
"""
Cria um novo filme
"""
cursor = self.conexao.cursor()
cursor.execute("INSERT INTO filmes (titulo, diretor, ano_lancamento, genero) VALUES (%s, %s, %s, %s)",
(filme.titulo, filme.diretor, filme.ano_lancamento, filme.genero))
self.conexao.commit()
return filme

def get_filme_by_id(self, id: int) -> FilmeResponse:
"""
Retorna um filme pelo seu id
"""
cursor = self.conexao.cursor()
cursor.execute("SELECT * FROM filmes WHERE id = %s", (id,))
row = cursor.fetchone()
if row is None:
return None
return FilmeResponse(
id=row[0],
titulo=row[1],
diretor=row[2],
ano_lancamento=row[3],
genero=row[4]
)
Binary file added requirements.txt
Binary file not shown.
17 changes: 17 additions & 0 deletions services/connectionService.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import os
from utils.db import mySqlDb
from models.repos.filmeRepoMySQL import FilmeRepoMySQL


def determinar_conexao():
"""
Determina qual conexão a ser utilizada com base no valor da variável de ambiente DB_TYPE.

Returns:
FilmeRepoInterface: Repositório de filmes
"""
if os.getenv('DB_TYPE') == 'mysql':
connectionMySql = mySqlDb.criar_conexao_mySql()
repo = FilmeRepoMySQL(connectionMySql)

return repo
28 changes: 28 additions & 0 deletions services/mySqlDbService.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class mySqlDbService:
def __init__(self, connection):
"""
Construtor da classe mySqlDbService.

Args:
connection (conexao mysql): Conexao com o banco de dados mysql
"""
self.connection = connection

def criar_banco(self, nome_banco):

"""
Cria um banco de dados no MySQL se ele não existir.

Esta função utiliza a conexão MySQL fornecida para criar um banco de dados
com o nome especificado. Se o banco de dados já existir, ele não será recriado.

Args:
nome_banco (str): O nome do banco de dados a ser criado.
"""

cursor = self.connection.cursor()
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {nome_banco}")
cursor.execute(f"USE {nome_banco}")
cursor.execute("CREATE TABLE IF NOT EXISTS filmes (id INT AUTO_INCREMENT PRIMARY KEY, titulo VARCHAR(255), diretor VARCHAR(255), ano_lancamento INT, genero VARCHAR(255))")
self.connection.commit()
print(f"Banco de dados '{nome_banco}' criado ou já existente.")
37 changes: 37 additions & 0 deletions utils/db/mySqlDb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import mysql.connector
import os
from services.mySqlDbService import mySqlDbService

def criar_conexao_mySql():
"""
Cria uma conex o com o banco de dados mySql.

Esta fun o cria uma conex o com o banco de dados mySql
utilizando a biblioteca mysql-connector-python. Ela
verifica se a conex o foi estabelecida com sucesso e
retorna a conex o criada.

Returns:
connection: Uma conex o com o banco de dados mySql
Raises:
Error: Se houver um erro ao conectar com o banco de dados
"""
try:
connection = mysql.connector.connect(
host='mysqldb',
port=3306,
user=os.getenv('MYSQL_USER'),
password=os.getenv('MYSQL_PASSWORD')
)
if connection.is_connected():
print("Conexão com o banco de dados mySql estabelecida com sucesso!")

dbService = mySqlDbService(connection)

dbService.criar_banco(os.getenv('MYSQL_DATABASE'))
connection.database = os.getenv('MYSQL_DATABASE')

return connection
except Exception as e:
print(f"Erro ao conectar com o banco de dados: {e}")
raise e