Code Monkey home page Code Monkey logo

fastapi-do-zero's Introduction

FastAPI do Zero

Esse é o repositório do material sobre FastAPI disponível em: fastapidozero.dunossauro.com.

O objetivo final desse curso é que ele também seja disponibilizado em vídeo quando a escrita do material terminar. Nos vemos no youtube em breve!

O objetivo desse projeto é ensinar FastAPI para pessoas que queiram ter o seu primeiro contato com o mesmo. A ideia padrão é construir uma aplicação pequena e simples, mas executando todos os passos até o deploy.

As Aulas estão dividas em:

  1. Apresentação do curso
  2. Configurando o Ambiente de Desenvolvimento
  3. Introdução ao desenvolvimento WEB
  4. Estruturando seu Projeto e Criando Rotas CRUD
  5. Configurando Banco de Dados e Gerenciando Migrações com Alembic
  6. Integrando Banco de Dados a API
  7. Autenticação e Autorização
  8. Refatorando a Estrutura do Projeto
  9. Tornando o sistema de autenticação robusto
  10. Criando Rotas CRUD para Tarefas
  11. Dockerizando a aplicação
  12. Automatizando os testes com integração contínua
  13. Fazendo o deploy no fly.io
  14. Despedida

Caso precise reconstruir o ambiente para as páginas

Sobre o ambiente

Todo esse projeto é gerenciado pelo Poetry, a versão usada durante o momento da escrita é 1.7.1:

pipx install poetry==1.8.2

A versão usada do python é a versão 3.12.2:

pyenv local 3.12.2

para configurar todo o ambiente basta executar:

poetry install

Sobre os comandos

Os comandos para executar funções como deploy, servidor local, geração de slides, etc. Estão todas sendo feitas pelo taskipy:

task --list
serve       Executa o servidor local do mkdocs
deploy      Faz o deploy da página em produção
slides      Gera os slides em pdf
slides_html Gera os slides em html (formato usado nas aulas)
pdf         Cria um pdf único de todo o curso (não otimizado ainda)

Para executar qualquer comando, basta usar: task <comando>

Sobre os slides

Todos os slides foram feitos usando marp. Versão do marp usada: 3.2.1. O tema rose-pine está dentro da pasta dos slides brutos.

fastapi-do-zero's People

Contributors

adorilson avatar aguynaldo avatar alphabraga avatar azmovi avatar bugelseif avatar dunossauro avatar gbpagano avatar henriqueccda avatar henriquesebastiao avatar ig0r-ferreira avatar ivansantiagojr avatar jonathanscheibel avatar julioformiga avatar lbmendes avatar lucasmpavelski avatar matheusalmeida28 avatar mmaachado avatar ricardo-emanuel01 avatar rodbv avatar rodrigosbarretos avatar vcwild avatar williangl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastapi-do-zero's Issues

Fixtures do Pytest e Freezegun

Problema

No tópico [REV] Tornando o sistema de autenticação robusto, na seção de testes de expiração de token, temos o seguinte código apresentado:

from freezegun import freeze_time

@freeze_time('2023-07-14 12:00:00')
def test_token_expiry(client, user, token):
    with freeze_time('2023-07-14 12:31:00'):
        response = client.delete(
            f'/users/{user.id}',
            headers={'Authorization': f'Bearer {token}'},
        )

    assert response.status_code == 401
    assert response.json() == {'detail': 'Could not validate credentials'}

No entanto, localmente, e pelas minhas pesquisas em outros lugares também, creio que a utilização do decorator freeze_time nesse caso afeta o teste, mas não afeta a fixture de geração de Token. O que faz com que o teste falhe.

Propostas de solução

  1. Uma das soluções encontradas foi usar algo como o pytest-freezegun, que usa markers do pytest pra conseguir fazer com que a fixture seja afetada, então teríamos algo do tipo:
from freezegun import freeze_time
from pytest_freezegun import pytest

@pytest.mark.freeze_time('2023-07-14 12:00:00')
def test_token_expiry(client, user, token):
    with freeze_time('2023-07-14 12:31:00'):
        response = client.delete(
            f'/users/{user.id}',
            headers={'Authorization': f'Bearer {token}'},
        )

    assert response.status_code == 401
    assert response.json() == {'detail': 'Could not validate credentials'}
  1. Outra solução encontrada foi a utilização do decorator freeze_time diretamente em uma fixture, dessa forma, sempre que essa fixture em questão for utilizada, ela vai estar num momento "congelado" (eu, particulamente, não sou muito fã dessa abordagem), algo assim:
@pytest.fixture
@freeze_time('2023-07-14 12:00:00')
def expired_token(client, user):
    response = client.post(
        '/token',
        data={'username': user.email, 'password': user.clean_password},
    )
    return response.json()['access_token']

Não sei se foi algum passo que acabei pulando ou se alguém mais passou por isso, mas esse foi um ponto que acabei encontrando. Seria massa se a gente pudesse, talvez, apontar isso durante o curso pra que ninguém fique travado sem entender porque esse teste não tá funcionando. O que acha? 😬

Revisão final aula 04 - Configurando o Banco de Dados e Gerenciando Migrações com Alembic

A aula não segue uma ordem gradual dos acontecimentos. A aula vai e volta em diversos cenários.

ORM -> 12 Fatores -> ORM -> engine/session -> modelo de dados -> Teste -> Configuração -> Alembic.

Isso complica BASTANTE o andamento da aula, pois os conceitos são travados e distanciados da aplicação. Uma grande ideia seria seguir de forma gradual. Por exemplo:

  • O que é um ORM: explicar os conceitos básicos
  • O SQLAlchemy: Afunilando o tópico anterior
    • Modelo de dados: Aqui definimos como será a tabela do banco de dados
    • Aqui explicar vamos entender os conceitos do SQLAlchemy nos baseando em um teste (Não montar a fixture aqui)
    • A engine: explicar como se comunicar com o banco de dados e usar em memória!
    • Criação da tabela usando os modelos de Base
    • A session: explicar o básico do funcionamento da Sessão
      • Aqui explicar o básico sobre os comandos da sessão:
        • .add
        • .commit
      • Buscando os dados no banco de dados
        • Falar sobre da função select select
        • Mostrar o print do select(User)
        • O método .select() -> Mostrar o print novamente
        • session.scalar: Mostrar que ele executa a busca real no banco
    • Aprimorando o sistema de testes (montar a fixture aqui)
  • Configuração do ambiente do banco de dados
    • 12 fatores
    • pydantic_settings
      • Instalação
      • Schema de configuração
    • arquivo .env
  • Migrações
  • ...

Esses são os pontos levados em consideração em relação à leitura em #67

Essa issue também deve levar em consideração os pontos já anotados em #67

Dúvida de qual pasta olhar?

Oi Pessoal, só uma dúvida quanto a organização. Cada aula se cria uma pasta com o numero da aula, isso mesmo ?
pergunto isso porque a aula 01 fica em um histórico de evolução por exemplo da aula 08. E assim por diante.

image

Revisar introduções e remover citações temporais

As introduções em alguns tópicos estão BASTANTE genéricas. A ideia é alterar os textos dos primeiros parágrafos das aulas para inserir um contexto mínimo!

Remover também as citações como "Na aula de hoje", "no código que fizemos hoje", ...

[Aula 01] Código do teste implementado falhando na task lint (pre_test)

Na aula 01 ficou faltando incluir uma linha em branco a mais nos trechos de código onde consta a função test_root_deve_retornar_200_e_ola_mundo() para respeitar a PEP8 (https://peps.python.org/pep-0008/#blank-lines) que pede nesses casos 2 linhas em branco.

Com isso ao rodar o teste, ao invés do esperado sucesso está ocorrendo falha na etapa de lint (pre_test):

(fast-zero-py3.12) lucas@pc:~/projetos/fast_zero$ task test
--- tests/test_app.py	2023-12-31 01:45:36.254314 +0000
+++ tests/test_app.py	2023-12-31 01:45:43.675734 +0000
@@ -1,7 +1,8 @@
 from fastapi.testclient import TestClient
 from fast_zero.app import app
+
 
 def test_root_deve_retornar_200_e_ola_mundo():
     client = TestClient(app)
 
     response = client.get('/')
would reformat tests/test_app.py

Oh no! 💥 💔 💥
1 file would be reformatted, 3 files would be left unchanged.
(fast-zero-py3.12) lucas@pc:~/projetos/fast_zero$

Lista de exercícos

Criar uma lista de exercícios para as aulas. Essa issue andará de acordo com as revisões em #67

  • 01
    • Criar um repositório no git com o projeto
  • 02
    • Crie um endpoint que retorna "olá mundo" usando HTML e escreva seu teste.
  • 03
    • Testar os casos de erros nos endpoints
    • Criar um endpoint de listagem de usuários por ID com testes
  • 04
    • Adicionar um campo de email na tabela e gerar uma nova migração
    • Adicionar um campo de quando o valor foi atualizado pela última vez (last_update) e gerar uma nova migração
  • 05
    • Implementar o banco de dados no endpoint de listagem de usuário por ID, (complementando o exercício da aula 03)
    • Atualizar os testes para os criados na aula 03 para contemplar o banco de dados
    • Escrever o teste para o enpoint de POST, com usuário já existente para validar o erro 400
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12

Ao final, criar um apêndice com as respostas dos exercícios #87

[Aula 05] - Adicionar o hash da senha também no endpoint de update

Em: Modificando os Endpoints para Usar a Autenticação temos diversos problemas.

  1. O nome do tópico deveria ser "Modificando endpoints usar o Hash de senhas"
  2. Adicionar o hash de senhas também no endpoit de update. Isso gera erros no update do token caso o user tente dar refresh do token, pois a senha do update está sendo armazenada de forma limpa (Encontrado pelo Ivan)

Algo como:

Modificando os Endpoints para Usar Hash de Senhas

Com as funções de criação de hash de senha e verificação de senha já criadas, agora devemos atualizar nossos endpoints que manipulam senhas para utilizar essas funções. Isso significa que, ao invés de armazenarmos a senha em sua forma original, armazenaremos seu hash, que é uma representação segura da mesma.

Endpoint de Criação de Usuário

Começaremos ajustando o endpoint de criação de usuários:

from fast_zero.security import get_password_hash

# ...

@app.post('/users/', response_model=UserPublic, status_code=201)
def create_user(user: UserSchema, session: Session = Depends(get_session)):
    db_user = session.scalar(select(User).where(User.email == user.email))
    if db_user:
        raise HTTPException(status_code=400, detail='Email already registered')

    hashed_password = get_password_hash(user.password)

    db_user = User(
        email=user.email,
        username=user.username,
        password=hashed_password,
    )
    session.add(db_user)
    session.commit()
    session.refresh(db_user)
    return db_user

Teste do Endpoint de Criação

Como a senha hashada é um processo interno e não afeta a resposta da API (que não deve retornar informações de senha), o teste já existente deve passar sem problemas:

task test

# ...

tests/test_app.py::test_root_deve_retornar_200_e_ola_mundo PASSED
tests/test_app.py::test_create_user PASSED

Endpoint de Atualização de Usuário

O mesmo procedimento deve ser feito para o endpoint de atualização, garantindo que, se a senha for alterada, ela seja armazenada como um hash:

@app.put('/users/{user_id}', response_model=UserPublic)
def update_user(user_id: int, user: UserUpdateSchema, session: Session = Depends(get_session)):
    db_user = session.scalar(select(User).where(User.id == user_id))
    if not db_user:
        raise HTTPException(status_code=404, detail='User not found')
    
    hashed_password = get_password_hash(user.password)
    user.password = hashed_password

    for key, value in user.dict().items():
        setattr(db_user, key, value)

    session.commit()
    session.refresh(db_user)
    return db_user

Teste do Endpoint de Atualização

...

Verbos no infinitivo

O texto está CHEIO dos meus vícios de linguagem.

  • Vamos fazer
  • Vamos criar
  • Iremos colocar

...

Padronizar a escrita usando os verbos no infinitivo. Faremos, Criaremos, Colocaremos, etc.

Sugestação sobre aula 2

Duno você acha que é valido colocar um hyperlink desse site iana quando estava comentando sobre o status code na aula 2?
screenshot_2024-02-26_09-19-58
Nesse site ele especifica todos as possíveis saídas do status code, não sei se seria interessante para a curiosidade do pessoal.

[Aula 07] - Notas sobre freezegun

Quando criei essa aula disse que explicaria o mínimo sobre o freezegun e acabei deixando uma nota {aqui você deve expandir esse tópico explicando um pouco mais sobre o freezegun} que ainda não foi preenchida

Volume não definido no serviço do docker

Não foi definido um volume no serviço de backend, aí as mudanças feitas localmente não refletem no container, sendo preciso reiniciá-lo toda vez. Mas quando defino um volume (.:/app) no serviço, gera um erro informando que o uvicorn (alembic, etc) não tá instalado.

Versão mínima obrigatória de Python

O FastAPI é construído com Python 3.7, inclusive isso é dito no primeiro parágrafo da configuração do ambiente. Entretanto, a versão 3.11 é colocada como necessária para o curso.

Portanto, qual é a versão mínima obrigatória? E qual o motivo da versão 3.11?

Essa dúvida surgiu porque uma pessoa teve problemas relacionados ao OpenSSL na instalação do pyenv. Analisando o ambiente dela, era um Ubuntu 20.04, que não tinha pacotes de python3.11, diferente do Ubuntu 22.04.

Aula 11- Deploy no fly precisa "[...] das suas informações de pagamento para continuar!"

Seguindo os passos da aula 11, um erro impede a continuação da reprodução dos passos descritos.

O comando flyctl launch --no-deploy após configurar a aplicação pelo navegador e clicar em Confirm Settings! o erro abaixo é apresentado:
image

A versão do flyctl usado é:
flyctl v0.1.135 linux/amd64 Commit: 4cd282a8ce995bec8fb408d880f159159eb209bc BuildDate: 2023-12-22T00:44:24Z
Foi usado o fastapi-ci-example para fazer este teste.

Como não está descrito na aula em questão que um método de pagamento é requerido, estou considerando isso um erro, mas acredito que não cabe a mim dizer se colocar no texto essa informação será suficiente ou se a troca para uma plataforma que prove esse serviço sem essa exigência (existe?). Como a aula já está pronta acredito que o mais simples e rápido seria o primeiro.

[Aula 05] - Problemas gerados por OAuth

Após a aula 05, com as alterações em #30 e #32, a base do OAuth passou a ser /auth/token. Representando um problma de logins em todas as aulas seguintes a isso!

Código atual no arquivo security.py:

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

Esse bloco de código deve mudar em todas as aulas que se sigam após a aula 05, dessa maneira:

oauth2_scheme = OAuth2PasswordBearer(tokenUrl='auth/token')

Erro nos códigos dos entryponts

Na aula seguinte a adição do docker, todos os arquivos de entrypoint estão errados. Para usar na minha máquina em live, estava usando originalmente a porta 8888, isso está nos arquivos da aula 09, 10 e 11

Health check e serviço de migration no Docker Compose

Olá ao pessoal do curso, principalmente o @dunossauro!

Tem um repositório de demonstração que eu estava baseando em partes do curso FastAPI do zero, mas como quando estava preparando a parte do Docker estava como um PR em andamento, procurei outras fontes.

Como exemplo base, tinha seguido a receita de bolo compartilhada nesse pequeno artigo do Simon Wilison para Django.

Ao contrário da parte atual do curso que é gerido por um entrypoint, naquela receita usa um serviço a parte que repliquei. Entretanto, ao rodar o serviço dava problemas do serviço de PostgreSQL não estar disponível ainda.

Considerando o que falei até agora, busquei sobre como colocar o requisito de disponibilidade dos serviços antes da aplicação FastAPI subir e fiz algumas mudanças:

  1. Um docker-compose.yml que estabelece esses requisitos para então a aplicação subir
  2. Um Dockerfile.dev que usa multi-stage build para gerar uma versão base e então configurar uma imagem para migração e outro para a aplicação em si

Considerando o intuito do curso ser uma introdução funcional do apanhado geral, não sei se seria bom abordar esses tópicos na aula de docker/docker compose, mas como em outras aplicações já passei por problemas de serviços bases não estarem de pé antes da aplicação, e dado o uso do curso para a base do repositório mencionado, achei de bom tom contribuir de volta inicialmente como uma issue e, se for o caso, posteriormente como uma PR

[Aula 06] - Arquivo .env não está no ignore

No tópico: Adicionando as constantes a Settings as variáveis de ambiente não são adicionadas ao arquivo .gitignore. Isso faz com que os segredos do projeto estejam expostos no repositório.

É necessaria uma alteração para que esse arquivo seja ignorado pelo git por questão de segurança.

Acredito que o melhor lugar para adicionar esse arquivo seja no tópico do Commit da aula.


Algo como:

Commit

Antes de efetuar nosso commit, temos um ponto de atençAo, o arquivo .env está guardando segredos do nosso ambiente, ele não deve ser exposto no repositíro. Para que isso não seja feito, é importante adicionarmos ele antes do commit em nosso arquivo .gitignore.

continuar aqui...

Para finalizar, vamos criar um commit para registrar todas as alterações que fizemos na nossa aplicação. Como essa é uma grande mudança que envolve reestruturar a forma como lidamos com as rotas e mover as constantes para variáveis de ambiente, podemos usar uma mensagem de commit descritiva que explique todas as principais alterações:

Revisão final do texto

Para liberar a versão final, revisar todas as aulas individualmente. O objetivo dessa issue não é reescrever nada, mas tentar aplicar todos os passos (fazer o curso de fato) e buscar por possíveis problemas (de código ou explicação) que podem ocorrer em algumas aulas. Validar códigos no Windows também é um passo importante.

  • Abertura
  • Aula 01 - Configurando o Ambiente de Desenvolvimento
    • Melhorias gerais no texto
    • Adicionar o isort no comando de lint (8a02446)
    • Adicionar o código dessa aula no repositório (045ecbc)
    • Trocar a dupla Isort + blue pelo Ruff (check e formatter)
    • #120
    • #122
  • Aula 02 - Introdução ao desenvolvimento WEB
  • Aula 03 - Estruturando o Projeto e Criando Rotas CRUD
  • Aula 04 - Configurando o Banco de Dados e Gerenciando Migrações com Alembic
    • #120
    • #122
    • #90
    • Melhorias gerais no texto #93
    • Remover comandos específicos de unix [touch] (001e7e5)
    • Comentar a linha do yield e levar ela para alguma referência
    • A tree da migration destacando a linha errada (fb52453)
    • Instalar sqlite3 no windows com winget: winget install --id SQLite.SQLite (3e4e48b)
    • Melhorar o texto e os commandos envolvendo o sqlite3. Não contar que pessoas tenham experiências anteriores com sqlite (14a3591)
      • Explicar que o sqlite3 é um shell (14a3591)
      • Explicar o que faz o comando .schema (14a3591)
      • Explicar o que faz o comando .exit (14a3591)
    • Explicar session.scalars e session.scalar
    • Comentar todo o bloco de código do teste
    • Fazer o link com a nova live de sqlalchemy
  • Aula 05 - Integrando Banco de Dados a API
    • #120
    • #122
    • Melhorias gerais no texto
    • Explicar o with na session (ou linkar)
    • Comentar todos os commandos da session commit, add, refresh, delete, ...
    • Explicar sobre query strings no endpoint de GET (Modificando o Endpoint GET /users)
    • Erro da ordem de imports no ConfigDict (8a02446)
    • No arquivo de exemplo da remoção de UserDB não existe a config_model (8a02446)
    • Explicar que não dá pra ter a cobertura de get_session
    • Em Modificando o Endpoint PUT /users remover if db_user is None: por if db_user (5cab0f2)
    • Fazer o link com a nova live de sqlalchemy
  • Aula 06 - Autenticação e Autorização com JWT
  • Aula 07 - Refatorando a Estrutura do Projeto
    • Melhorias gerais no texto
    • Investigar #74
    • Cobrir o caso do PUT atuando como POST
    • #102
    • #120
    • #121
    • #122
  • Aula 08 - Tornando o sistema de autenticação robusto
  • Aula 09 - Criando Rotas CRUD para Gerenciamento de Tarefas em FastAPI
  • Aula 10 - Dockerizando a nossa aplicação e introduzindo o PostgreSQL
  • Aula 11 - Automatizando os testes com Integração Contínua (CI)
  • Aula 12 - Fazendo deploy no Fly.io e configurando o PostgreSQL
    • Melhorias gerais no texto
    • #121
    • #122
    • Fly faz build no windows corretamente?
  • Despedida
    • Melhorias gerais no texto
  • Appendices #87
    • Resolução dos exercícios #90
      • Aula 01
      • Aula 02
      • Aula 03
        • Exercício 01
        • Exercício 02
        • Exercício 03
      • Aula 05
        • Exercício 01
        • Exercício 02
        • Exercício 03

Quando essa issue for finalizada, o curso em texto está em sua primeira versão estável!

  • post edit 23/jan/2024: A inclusão da nova aula 02 foi adicionada a essa issue
  • post edit 04/fev/2024: Inclusão de alguns pontos de melhoria
  • post edit 28/fev/2024: Inclusão da issue #102 nos planos de revisão, inclusão da live de sqlalchemy, testes para erro de decode
  • post edit 02/mar/2024: Testes no windows realizados até aula 09. Tudo OK!
  • post edit 01/abr/2024: Adição de #90, #120 , #121 e #122

Texto de despedida!

  • Próximos passos
  • Remover a palavra introdução
  • Lincar mais materiais disponíveis sobre FastAPI como do Cassio

Criar um README com instruções!

Issue totalmente focada no eu do futuro!

Criar uma documentação no README de como rodar/executar esse projeto de forma detalhada. Caso alguma coisa precise ser alterada no futuro. Como os comandos do taskipy, poetry, etc.

Problema ao escrever o teste do exercício 02

Não estou conseguindo capturar o texto do html, tentei usar a dica do response.text mas talvez eu não esteja usando da forma correta. Segue os códigos:

Teste:

from fastapi.testclient import TestClient

from fast_zero.aula_00 import app

def test_exercicio_deve_retornar_um_ola_mundo_e_um_codigo_200():
   cliente = TestClient(app)
   response = cliente.get('/exercicio')

   assert response.status_code == 200
   assert response.text == 'Olá mundo'

App:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get('/exercicio', status_code=200, response_class=HTMLResponse)
def read_exercicio():
    html_content = """
    <html>
    <head>
    <title>
    Exercício 01
    </title>
    </head>
    <body>
    <h1>Olá mundo</h1>
    </body>
    </html>"""
    return HTMLResponse(content=html_content, status_code=200)

[Aula 07] Efeito colateral da fixture `user` usando UserFactory

Nota: o titulo dessa issue foi alterado o contexto por atualizações no desenvolvimento da parte 07 do curso
Titulo anterior: [Aula 07] Criar nota sobre ordem dos testes afetar o resultado ao usar o factory-boy

Quando implementei as atualizações dos testes usando o factory-boy na rota de atualização das informações do usuário, demorei um tempo para entender porque os testes estavam dando erro por causa do id do usuário, com isso gostaria de fazer uma sugestão para alertar isso para usuários novatos (como eu) com essas implementações mais complexas que a ordem dos testes afetam seu resultado

Nota do ambiente

  • Windows 10
    • python v3.11.4
      • poetry v1.7.1
        • pytest v7.4.3
        • factory-boy v3.3.0
fast_todo/routes/auth.py
# Resto do código...

@router.post('/token', response_model=Token)
def login_for_access_token(
    form_data: OAuth2Form,
    session: Session,
):
    invalid_access = HTTPException(
        status_code=400, detail='Email ou senha inválido'
    )

    user = session.scalar(select(User).where(User.email == form_data.username))

    if not user:
        raise invalid_access

    if not verify_password(form_data.password, user.password):
        raise invalid_access

    access_token = create_access_token(data={'sub': user.email})

    return {'access_token': access_token, 'token_type': 'bearer'}
fast_todo/routes/users.py
# Resto do código...

@router.put('/{user_id}', status_code=200, response_model=UserPublic)
def update_user(
    user_id: int,
    user: UserSchema,
    session: Session,
    current_user: CurrentUser,
):
    if current_user.id != user_id:
        raise HTTPException(status_code=400, detail='Permissões insuficientes')

    current_user.username = user.username
    current_user.password = user.password
    current_user.email = user.email

    session.commit()
    session.refresh(current_user)

    return current_user

# Resto do código...
tests/conftest.py
import factory
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import StaticPool, create_engine
from sqlalchemy.orm import sessionmaker

from fast_todo.app import app
from fast_todo.database import get_session
from fast_todo.models import Base, User
from fast_todo.security import get_password_hash


class UserFactory(factory.Factory):
    class Meta:
        model = User

    id = factory.Sequence(lambda n: n)
    username = factory.LazyAttribute(lambda obj: f'test{obj.id}')
    email = factory.LazyAttribute(lambda obj: f'{obj.username}@test.com')
    password = factory.LazyAttribute(lambda obj: f'{obj.username}@example.com')


@pytest.fixture
def session():
    engine = create_engine(
        'sqlite:///:memory:',
        connect_args={'check_same_thread': False},
        poolclass=StaticPool,
    )

    Session = sessionmaker(bind=engine)
    Base.metadata.create_all(engine)

    yield Session()

    Base.metadata.drop_all(engine)


@pytest.fixture
def client(session):
    def get_session_override():
        return session

    with TestClient(app) as client:
        app.dependency_overrides[get_session] = get_session_override
        yield client

    app.dependency_overrides.clear()


@pytest.fixture
def user(session):
    password = 'secret'
    user = UserFactory(password=get_password_hash(password))

    session.add(user)
    session.commit()
    session.refresh(user)

    user.clean_password = password

    return user


@pytest.fixture
def other_user(session):
    password = 'secret'
    user = UserFactory(password=get_password_hash(password))

    session.add(user)
    session.commit()
    session.refresh(user)

    user.clean_password = password

    return user


@pytest.fixture
def token(client, user):
    response = client.post(
        '/auth/token',
        data={'username': user.email, 'password': user.clean_password},
    )

    json = response.json()
    return json['access_token']
test/test_users.py
from fast_todo.schemas import UserPublic


def test_update_user(client, user, token):
    response = client.put(
        f'/users/{user.id}',
        headers={'Authorization': f'Bearer {token}'},
        json={
            'username': 'new_user',
            'email': '[email protected]',
            'password': 'new_pass',
        },
    )

    assert response.status_code == 200
    assert response.json() == {
        'id': 1,
        'username': 'new_user',
        'email': '[email protected]',
    }


def test_update_user_with_wrong_user(client, other_user, token):
    response = client.put(
        f'/users/{other_user.id}',
        headers={'Authorization': f'Bearer {token}'},
        json={
            'username': 'invalid_user',
            'email': '[email protected]',
            'password': 'wrong_password',
        },
    )

    assert response.status_code == 400
    assert response.json() == {'detail': 'Permissões insuficientes'}

# Resto do código...

Na criação dos meus testes deixava na ordem:

  • Testes de criação do usuário
  • Testes de obter usuários
  • Testes de atualizar informações dos usuários
  • Testes de remover usuários

Não tenho certeza se fiz algo de errado, mas só passou nos testes quando deixei na ordem:

  • Testes de atualizar informações dos usuários
  • Testes de criação do usuário
  • Testes de obter usuários
  • Testes de remover usuários

Apêndices

A ideia dessa issue é reunir insumos para possíveis apêndices no material. No sentido de termos mais aulas, não vinculadas originalmente ao conteúdo, mas que se apoiem no fato dos conceitos básicos já terem sido explorados no material geral.

Por exemplo:

  1. Interagindo com templates Jinja
  2. Banco de dados de documentos com Beanie
  3. Deploy em uma VPS
  4. Gunicorn + Uvicorn
  5. Tarefas em Backgroud
  6. Gerenciamento de usuários com FastAPI-Users
  7. Exercícios resolvidos
  8. ...

Temas que podem se somar aos fundamentos já explicados e expandir os horizontes com FastAPI.

A prioridade dessa issue é extremamente baixa, são só ideias na minha cabeça. Quem sabe esse material não pode até ser desenvolvido de forma colaborativa. São só ideias!

Quem quiser e puder, pode se sentir confortável para conversarmos nessa issue.

Vídeo Aulas

Cai de paraquedas nesse repositório e vi agora o site, queria saber se tem em formato de vídeo também ou apenas a "doc" do curso... Para mim é fácil e até gosto mais assim, com uma bela "documentação" do curso, onde posso recorrer a qualquer momento, mas para amigos e alguns devs que Jr que treino no meu time vídeos são mais fáceis.

Farante

Acredito que seja farão a palavra correta

Item 3 da integração continua

"Instalar as dependências do projeto: farante que todas as bibliotecas necessárias estão disponíveis para a execução dos testes."

Repositórios

Essa issue foi feita para você compartilhar o seu repositório com todas as pessoas que estão fazendo o curso e aprender um pouco com quem também está fazendo.

Compartilhe seu repositório da seguinte forma:

Link do projeto Seu @ no git Comentário (opcional)
fast_zero @dunossauro Implementação do material do curso sem alterações

Montarei uma tabela juntado todos os repositórios.

1º parágrafo

Legibilidade nível 8:

"Olá, boas vindas ao curso de FastAPI!

A nossa intenção neste curso é facilitar o aprender a desenvolver APIs usando o FastAPI. Vamos explorar como integrar bancos de dados, criar testes e um sistema básico de autenticação. Tudo isso para oferecer uma boa base para quem quer trabalhar com essa tecnologia. A nossa forma de apresentar o curso é prática e cheia de informações. Ela busca trazer o que precisa para criar os nossos próprios projetos."

[Aula 09] Erro no código de exemplo do conftest.py com a sessão do Postgres

Tópico onde o erro foi encontrado: Executando os testes no PostgreSQL

Local dos erros:
09_errors

O meu código funcionou apenas quando deixei assim:

import factory
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import StaticPool, create_engine
from sqlalchemy.orm import sessionmaker

from fast_todo.app import app
from fast_todo.database import get_session
from fast_todo.models import Base, User
from fast_todo.settings import Settings
from fast_todo.security import get_password_hash

# Resto do código...

@pytest.fixture
def session():
    engine = create_engine(
        Settings().DATABASE_URL,
        poolclass=StaticPool,
    )

    Session = sessionmaker(bind=engine)
    Base.metadata.create_all(engine)

    yield Session()

    Base.metadata.drop_all(engine)

# Resto do código...

Pois quando executava sem o StaticPool depois que ele passava do teste de auth.py o do app.py ficava parado sem executar, então supus que poderia ser por causa de não ter as configurações de execução que você explicou no começo da aula que são executadas de forma paralela

Ideia para introduzir os conceitos iniciais de forma gradual nas aulas 01 e 02

Em: Primeira Execução de um "Hello, World!"

Escrever um pouco mais sobre a função de uma aplicação web. Começar somente com a função

def read_root():
    return {'message': 'Olá Mundo!'}

E mostrar que é uma função normal do python, que funciona e exibe o resultado quando a executamos em nossa máquina. Que o objetivo de uma aplicação web é que essas funções possam ser chamadas de forma remota. Para isso usamos uma biblioteca como o FastAPI. Assim, damos um endereço para que essa função possa ser acessada por um servidor.

Aí adicionamos o fastAPI. Mostrando que o get é o / e com isso vamos conseguir chamar essa função e obter seu resultado remotamente.

Adicionar os bangs corretamente nesse código, os comentários estão aparecendo no código final

Em seguida, na aula 02, antes de introduzir o http em O que é uma API?, podemos mudar o schema do pydantic e introduzir o openAPI spec. Mostrando que parte do que é um API é formalizar as respostas que vamos receber quando a função for chamada. Como uma espécie de documentação.

class Message(BaseModel):
    detail: str

Logo após definir isso, mostramos o /docs e vemos que o schema já está lá presente. Nisso entra o gatilho para falar sobre o O que é HTTP?

Aí no HTTP formalizamos o acesso ao / e mostramos que o que está especificado no endpoint é exatamente o que será retornado na documentação! Nisso já explicamos o 200 e o GET. Dando gancho para aula toda

Dessa forma tudo fica linear e gradual!

Isso pode ser feito com as revisões de #67. Voltando a aula 01 e alterando o início da aula 02

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.