Skip to content
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Arquivos de dados locais
/data/
*.db
*.sqlite3

# Arquivos IDE
.vscode/
.idea/
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3.10-slim

WORKDIR /app

COPY ./requirements.txt /app/requirements.txt

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

COPY ./app /app/app

EXPOSE 8000


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

#### Descrição
Esta é uma implementação do desafio de CRUD de Filmes utilizando Python, FastAPI, SQLAlchemy (com SQLite) e Docker/Docker-compose.

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.
A aplicação expõe uma API REST para criar e listar filmes, garantindo a persistência dos dados através de um volume Docker.

Rotas da API:
## Tecnologias Utilizadas

- `/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.
* **Python 3.10**
* **FastAPI**: Para a construção da API e documentação automática (Swagger/ReDoc).
* **Uvicorn**: Servidor ASGI para rodar o FastAPI.
* **SQLAlchemy**: ORM para interação com o banco de dados.
* **SQLite**: Banco de dados relacional file-based para persistência.
* **Docker & Docker-compose**: Para containerização da aplicação e gerenciamento da persistência de dados.

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!
## Como Subir a Aplicação

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

- 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)
* [Docker](https://www.docker.com/get-started) instalado.
* [Docker Compose](https://docs.docker.com/compose/install/) instalado (geralmente vem com o Docker Desktop).

### Passos para Execução

#### Como começar?
1. Clone este repositório (ou o fork que você criou).
2. Navegue até o diretório raiz do projeto (onde o arquivo `docker-compose.yml` está localizado).
3. Execute o seguinte comando no seu terminal:

- 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
```bash
docker-compose up --build
```

#### Dúvidas?
* O comando `--build` força o Docker a construir a imagem a partir do `Dockerfile` na primeira execução (ou se houver mudanças nos arquivos).
* Se preferir rodar em modo "detached" (em segundo plano), use `docker-compose up -d --build`.

Qualquer dúvida / sugestão / melhoria / orientação adicional só enviar email para [email protected]
4. Pronto! A API estará rodando.

Salve!
### Acessando a API

Após executar o `docker-compose up`, a API estará acessível nos seguintes endereços:

* **API (Raiz)**: [http://localhost:8000/](http://localhost:8000/):
<img src="evidencias/localhost.png" alt="" />

* **Documentação Interativa (Swagger UI)**: [http://localhost:8000/docs](http://localhost:8000/docs):
<img src="evidencias/endpoints.png" alt="" />

* **Documentação Alternativa (ReDoc)**: [http://localhost:8000/redoc](http://localhost:8000/redoc):


### Endpoints Disponíveis

* `POST /filmes`: Cadastra um novo filme.
* `GET /filmes`: Retorna todos os filmes cadastrados.
* `GET /filmes/{id}`: Retorna um filme específico pelo seu ID.
Empty file added app/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions app/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from sqlalchemy.orm import Session
from . import models, schemas

def get_filme(db: Session, filme_id: int):
return db.query(models.Filme).filter(models.Filme.id == filme_id).first()

def get_filmes(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Filme).offset(skip).limit(limit).all()

def create_filme(db: Session, filme: schemas.FilmeCreate):
db_filme = models.Filme(
titulo=filme.titulo,
diretor=filme.diretor,
ano=filme.ano
)
db.add(db_filme)
db.commit()
db.refresh(db_filme)
return db_filme
26 changes: 26 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os

DATA_DIR = "/app/data"
if not os.path.exists(DATA_DIR):
os.makedirs(DATA_DIR)

# sqlite, volume do Docker
SQLALCHEMY_DATABASE_URL = f"sqlite:///{DATA_DIR}/filmes.db"

engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

# obter a sessão do bd em cada request
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
40 changes: 40 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List

from . import crud, models, schemas
from .database import SessionLocal, engine, get_db

models.Base.metadata.create_all(bind=engine)

app = FastAPI(
title="API de CRUD de Filmes",
description="Desafio tecnico de Roberto Henrique Duarte para um CRUD da Wattio.",
version="1.0.0"
)

@app.post("/filmes", response_model=schemas.Filme, status_code=201)
def create_filme_endpoint(filme: schemas.FilmeCreate, db: Session = Depends(get_db)):

return crud.create_filme(db=db, filme=filme)


@app.get("/filmes", response_model=List[schemas.Filme])
def read_filmes_endpoint(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):

filmes = crud.get_filmes(db, skip=skip, limit=limit)
return filmes


@app.get("/filmes/{filme_id}", response_model=schemas.Filme)
def read_filme_endpoint(filme_id: int, db: Session = Depends(get_db)):

db_filme = crud.get_filme(db, filme_id=filme_id)
if db_filme is None:
raise HTTPException(status_code=404, detail="Filme não encontrado")
return db_filme

@app.get("/")
def read_root():
""" verificar se a API está online."""
return {"message": "Bem-vindo a API de Filmes da Wattio🤖. Espero que gostem e possamos fazer parte da mesma equipe em breve! Acesse /docs para ver a documentação."}
10 changes: 10 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from sqlalchemy import Column, Integer, String
from .database import Base

class Filme(Base):
__tablename__ = "filmes"

id = Column(Integer, primary_key=True, index=True)
titulo = Column(String, index=True)
diretor = Column(String)
ano = Column(Integer)
16 changes: 16 additions & 0 deletions app/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pydantic import BaseModel
from typing import Optional

class FilmeBase(BaseModel):
titulo: str
diretor: str
ano: int

class FilmeCreate(FilmeBase):
pass

class Filme(FilmeBase):
id: int

class Config:
from_attributes = True
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: '3.8'

services:
api:
build: .
container_name: api_filmes_crud
ports:
- "8000:8000"
volumes:
# Cria um volume nomeado 'api_data' e o monta no diretório /app/data dentro do container. É aqui que o nosso arquivo 'filmes.db' (SQLite) será armazenado.
- api_data:/app/data

volumes:
api_data:
Binary file added evidencias/endpoints.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added evidencias/localhost.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fastapi
uvicorn[standard]
SQLAlchemy
pydantic