Commit inicial
This commit is contained in:
commit
5385af660d
10 changed files with 173 additions and 0 deletions
13
.env.example
Normal file
13
.env.example
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Copie estas variáveis para o .env e preencha os valores
|
||||||
|
|
||||||
|
# SQL Server
|
||||||
|
DB_HOST=
|
||||||
|
DB_PORTA=1433
|
||||||
|
DB_DATABASE=
|
||||||
|
DB_LOGIN=
|
||||||
|
DB_SENHA=
|
||||||
|
|
||||||
|
# Opcionais (já têm valor padrão)
|
||||||
|
# DB_DRIVER=ODBC Driver 17 for SQL Server
|
||||||
|
# DB_POOL_SIZE=5
|
||||||
|
# DB_POOL_MAX_OVERFLOW=10
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.log
|
||||||
33
app/core/configs.py
Normal file
33
app/core/configs.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic import SecretStr
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
# Raiz do projeto (api-podium/), pra achar o .env independente de onde a API for iniciada
|
||||||
|
RAIZ_PROJETO = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=RAIZ_PROJETO / '.env',
|
||||||
|
env_file_encoding='utf-8',
|
||||||
|
env_ignore_empty=True, # variável vazia no .env conta como não configurada
|
||||||
|
extra='ignore', # outras variáveis no .env não geram erro
|
||||||
|
)
|
||||||
|
|
||||||
|
# SQL Server
|
||||||
|
db_host: str
|
||||||
|
db_porta: int = 1433
|
||||||
|
db_database: str
|
||||||
|
db_login: str
|
||||||
|
db_senha: SecretStr # SecretStr evita que a senha apareça em print/log
|
||||||
|
db_driver: str = 'ODBC Driver 17 for SQL Server'
|
||||||
|
|
||||||
|
# Pool de conexões
|
||||||
|
db_pool_size: int = 5
|
||||||
|
db_pool_max_overflow: int = 10
|
||||||
|
|
||||||
|
# Configs da api
|
||||||
|
port: int
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
47
app/core/database.py
Normal file
47
app/core/database.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import pyodbc
|
||||||
|
from fastapi import Depends
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.engine import URL
|
||||||
|
from sqlalchemy.pool import PoolProxiedConnection
|
||||||
|
|
||||||
|
from app.core.configs import settings
|
||||||
|
|
||||||
|
# O pool do SQLAlchemy substitui o pool interno do ODBC. Com os dois ligados,
|
||||||
|
# conexões que o SQLAlchemy descarta continuam abertas no driver.
|
||||||
|
pyodbc.pooling = False
|
||||||
|
|
||||||
|
|
||||||
|
def _escapar(valor: str) -> str:
|
||||||
|
# Envolve o valor em chaves pra que ; ou } na senha não quebrem a connection string
|
||||||
|
return '{' + valor.replace('}', '}}') + '}'
|
||||||
|
|
||||||
|
|
||||||
|
connection_string = (
|
||||||
|
f"DRIVER={_escapar(settings.db_driver)};"
|
||||||
|
f"SERVER={settings.db_host},{settings.db_porta};"
|
||||||
|
f"DATABASE={_escapar(settings.db_database)};"
|
||||||
|
f"UID={_escapar(settings.db_login)};"
|
||||||
|
f"PWD={_escapar(settings.db_senha.get_secret_value())};"
|
||||||
|
)
|
||||||
|
|
||||||
|
engine = create_engine(
|
||||||
|
URL.create('mssql+pyodbc', query={'odbc_connect': connection_string}),
|
||||||
|
pool_size=settings.db_pool_size, # conexões mantidas abertas
|
||||||
|
max_overflow=settings.db_pool_max_overflow, # extras permitidas em pico de acesso
|
||||||
|
pool_pre_ping=True, # testa a conexão antes de entregar (o servidor pode ter derrubado)
|
||||||
|
pool_recycle=1800, # renova conexões com mais de 30 min
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_conexao() -> Iterator[PoolProxiedConnection]:
|
||||||
|
"""Pega uma conexão do pool e devolve ao final da requisição."""
|
||||||
|
conexao = engine.raw_connection()
|
||||||
|
try:
|
||||||
|
yield conexao
|
||||||
|
finally:
|
||||||
|
conexao.close() # devolve ao pool, não fecha de verdade
|
||||||
|
|
||||||
|
Conexao = Annotated[PoolProxiedConnection, Depends(get_conexao)]
|
||||||
7
app/v1/api.py
Normal file
7
app/v1/api.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from app.v1.modules.veiculo.router import veiculo_router
|
||||||
|
|
||||||
|
api_router = APIRouter()
|
||||||
|
|
||||||
|
api_router.include_router(veiculo_router)
|
||||||
0
app/v1/modules/escrituracao/router.py
Normal file
0
app/v1/modules/escrituracao/router.py
Normal file
5
app/v1/modules/veiculo/model.py
Normal file
5
app/v1/modules/veiculo/model.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class CancelamentoPropostaInput(BaseModel):
|
||||||
|
id: str
|
||||||
30
app/v1/modules/veiculo/router.py
Normal file
30
app/v1/modules/veiculo/router.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
|
||||||
|
from app.core.database import Conexao
|
||||||
|
from app.v1.modules.veiculo.model import CancelamentoPropostaInput
|
||||||
|
from app.v1.modules.veiculo.services import CancelamentoProposta
|
||||||
|
|
||||||
|
veiculo_router = APIRouter(
|
||||||
|
prefix='/veiculos'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rota de cancelamento (logica pra informar que o processo foi cancelado, esses vendedores que ficam crinando proposta pra dps cancelar é foda)
|
||||||
|
@veiculo_router.post(
|
||||||
|
path='/cancelar_proposta'
|
||||||
|
)
|
||||||
|
def cancelar_proposta(
|
||||||
|
dados_proposta_holmes: CancelamentoPropostaInput,
|
||||||
|
conexao: Conexao,
|
||||||
|
):
|
||||||
|
repo = CancelamentoProposta(conexao)
|
||||||
|
|
||||||
|
cancelou = repo.cancelar_no_banco(dados_proposta_holmes.id)
|
||||||
|
|
||||||
|
if not cancelou:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code= status.HTTP_404_NOT_FOUND,
|
||||||
|
detail= "Proposta não encontrada no banco de dados"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
'mensagem': 'Proposta cancelada'
|
||||||
|
}
|
||||||
17
app/v1/modules/veiculo/services.py
Normal file
17
app/v1/modules/veiculo/services.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from sqlalchemy.pool import PoolProxiedConnection
|
||||||
|
|
||||||
|
|
||||||
|
class CancelamentoProposta:
|
||||||
|
def __init__(self, conexao: PoolProxiedConnection) -> None:
|
||||||
|
self.conexao = conexao
|
||||||
|
|
||||||
|
def cancelar_no_banco(self, id_processo: str) -> bool:
|
||||||
|
cursor = self.conexao.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE HOLMES_FAT_ESTOQUE
|
||||||
|
SET STATUS = 'CANCELADO', DTA_ALTERACAO_STATUS = GETDATE()
|
||||||
|
WHERE ID_PROCESSO = ?
|
||||||
|
""", id_processo)
|
||||||
|
|
||||||
|
self.conexao.commit()
|
||||||
|
return cursor.rowcount > 0
|
||||||
15
main.py
Normal file
15
main.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from app.core.configs import settings
|
||||||
|
from app.v1.api import api_router
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title = 'API - Podium',
|
||||||
|
version= '0.0.1'
|
||||||
|
)
|
||||||
|
|
||||||
|
app.include_router(api_router, prefix='/v1')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
uvicorn.run(app, host='127.0.0.1', port=settings.port)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue