BravaWeb Framework for ASGI Server
Project description
BravaWeb Framework for ASGI Server
Framework para aplicações WEB baseada em Python3 ASGI (Asynchronous Server Gateway Interfac em Uvicorn), com possibilidade de utilização de Template em Html (Mako Templates).
Veja Documentação em:
Uvicorn: https://www.uvicorn.org/ Mako Templates: https://www.makotemplates.org/
Instalação
Instalação utilizando Pip
pip install bravaweb
Git/Clone
git clone https://github.com/robertons/bravaweb
cd bravaweb
pip install -r requirements.txt
python setup.py install
Primeiros Passos
Inicie seu projeto conforme estrutura abaixo
app
├── ...
├── configuration
│ ├── __init__.py
│ └── api.py
└── server.py
O arquivo de configurações deve conter os seguintes dados:
variável | tipo | obrigatório | descrição |
---|---|---|---|
directory | string | sim | Caminho Projeto |
encoding | string | sim | Codificação |
date_format | string | sim | Formato data |
short_date_format | string | sim | Formato data curta |
token | string | sim | Token codificação Authorization Header |
domains | array | sim | Domínios autorizados a acessar |
access_exceptions | array | sim | Rotas e Exceções de acesso |
routes | array | sim | Rotas do Projeto |
configuration/__init__.py
# -*- coding: utf-8 -*-
from configuration import api
configuration/api.py
# -*- coding: utf-8 -*-
import os
# Directory
directory = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
# Api Encoding
encoding = "utf-8"
# Date Format
date_format = "%d/%m/%Y %H:%M:%S"
short_date_format = "%d/%m/%Y"
# Token Authorization
token = "JWT-Token-Project"
# Authorized Domains Origin/Referrer
domains = [
"https://www.dominio.com.br",
"https://alias.dominio.com.br",
]
# Exceptions Routes
access_exceptions = [
{'path': '(^/default/)','referer': '*'},
{'path': '(/rota/especifica/)','referer': '(^https://dominio.especifico.com.br/)'},
{'path': '*', 'referer': "(^https://outro.dominio.com.br/)|(^https://adicional.dominio.com.br/)"},
]
routes = [
("{controller}/{area}/{module}/{action}/{id}", '(^/admin/)|(^/panel/)',
("{controller}/{module}/{action}/{id}", ""),
]
Definições:
domains: lista array de strings, com domínios que tem acesso a api, o teste é feito baseado no origin e/ou referrer de cada requisição.
access_exceptions: é possivel que algumas rotas sejam abertas para qualquer requisição, ou mesmo que alguma rota seja especifica para algum domínio. A lista deve conter um dicionário com as chaves path e referer onde:
path: é referente ao caminho da rota
referer: origem da requisição
Ambos os valores aceitam * para todos ou expressão regular para teste de string.
routes: lista com tuplas que definem as rotas padrões do projeto. Bravaweb esta preparado para até 4 níveis de profundidade que definem, Controlador, Area, Modulo, Ação e mais um nível opcional para captação de ID, a prioridade das regras é sequencial, portanto as regras específicas devem vir primeiro. A tupla é definida assim:
0: a captação de cada parte da profundidade para carregamento
1: expressão regular para identificar a regra
Por padrão os valores de rota do ambiente são:
controller = None
area = None
module = "default"
action="index"
id = None
Por fim vamos criar a execução do projeto que vai tratar as requisições e processar as rotas.
O arquivo server.py
na raiz deve ficar assim:
#-*- coding: utf-8 -*-
import sys
import configuration
from bravaweb import App as application
Acesse o diretório do seu projeto, e execute o comando de serviço do ASGI, conforme documentação do Uvicorn, no exemplo abaixo ativamos o ambiente virtual onde os pacotes estão instalados:
source ../env/bin/activate
uvicorn server:application --port 8080 --interface=asgi3 --workers 7 --proxy-headers --lifespan off --reload
O Resultado então será:
INFO: Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit)
INFO: Started reloader process [82503] using statreload
INFO: Started server process [82505]
.
.
.
Neste momento sua aplicação estará em execução. Nós configuramos as rotas mas não desenvolvemos nenhuma delas portanto qualquer requisição na url http://127.0.0.1:8080 irá retornar 404.
Hello World
Vamos iniciar aplicando a rota default, a pasta do projeto nesse momento deverá estar assim:
app
├── ...
├── configuration
│ ├── __init__.py
│ └── api.py
├── controllers
│ └── default.py
└── server.py
Conforme exemplificado a rota default(padrão) é
controller = None
area = None
module = "default"
action="index"
id = None
O arquivo ficará assim:
controllers/default.py
# -*- coding: utf-8 -*-
from bravaweb.controller import *
class DefaultController(Controller):
@get
async def index(self) -> Json:
await View(self.enviroment, data={"mensagem": 'Olá Mundo'})
Analisando a rota default:
Nome do Controlador é default, por isso nome da classe é DefaultController, herdando o controlador do framework (Controller)
O metodo de request aceito para esta rota é o GET (@get) , mas POST (@post) , PUT(@put) e DELETE(@delete) também são aceitos. Uma requisição diferente do permitido para rota retorna Erro 405: Method not allowed
O Framwork é baseado em ASGI (Asynchronous Server Gateway Interface) por isso ação index é assíncrona (async) .
A anotação é o tipo de resultado que essa rota irá retornar, posteriormente veremos sobre os tipos, no exemplo acima utilizamos Json.
Todos os dados da requisição, estão na enviroment, veremos mais logo a seguir.
Para melhor compreenção sobre as rotas , vejamos os exemplos abaixo baseado no arquivo de configuração acima:
Criando e Configurando Rotas
Os padrões de rota é configurado no arquivo de configurações em routes. Você provavelmente fará isso somente uma vez, ou quando for necessária a criação de rotas específicas em seu projeto. Abaixo segue alguns exemplos baseado na configuração que apresentamos.
Exemplo 1
GET -> api.dominio.com.br/admin/catalog/products/list
A regra identificada é a primeira da lista, pois o path da request inicia com /admin/ conforme expressão regular da posição [1] da tupla em configuration.api.routes:
("{controller}/{area}/{module}/{action}/{id}", '(^/admin/)|(^/panel/)'
O resultado da captação da rota conforme posição [0] da tupla será:
controller = 'admin'
area = 'catalog'
module = 'products'
action = 'list'
A estrutura para processamento desta rota devera ser:
app
├── ...
├── controllers
│ └── admin
│ │ └── catalog
│ │ | └── products.py
O arquivo :
controllers/admin/catalog/products.py
# -*- coding: utf-8 -*-
from bravaweb.controller import *
class ProductsController(Controller):
@get
async def list(self) -> Json:
await View(self.enviroment, data=[{"prod_nome": 'Exemplo'}])
Exemplo 2
POST -> api.dominio.com.br/site/product/like/110
A regra identificada é a default (segunda da lista), pois o path da request não contempla as expressões regulares anteriores :
("{controller}/{module}/{action}/{id}", "")
O resultado da captação da rota conforme posição [0] da tupla será:
controller = 'site'
area = None
module = 'product'
action = 'like'
id = 110
A estrutura para processamento desta rota devera ser:
app
├── ...
├── controllers
│ └── site
│ │ └── product.py
O arquivo:
controllers/site/products.py
# -*- coding: utf-8 -*-
from bravaweb.controller import *
class ProductController(Controller):
@post
async def like(self) -> Json:
await View(self.enviroment, data=[{"likes": 535}])
Ambiente / Enviroment
A qualquer momento dentro do controlador é possivel acessar os dados da requisição através de self.enviroment os dados disponíves são:
Campo | Tipo | descrição |
---|---|---|
origin | string | Origem ou Referrer da Requisição |
remote_ip | string | Ip do usuário |
remote_uuid | string | UUID se informado no header |
browser | string | Browser do usuário |
accept_encoding | string | tipos de codificação aceito pelo browser |
method | string | metodo da requisição (GET, POST, PUT ou DELETE) |
response_type | string | tipo de resposta esperada para requisição |
authorization | string | Token JWT - Bearer enviado no Header |
bearer | string | Token JWT decodificado |
content_length | int | Tamanho da requisição |
get | dict | Dados enviados por querystring |
post | dict | Dados enviados por post |
body | bytes | Bytes do corpo da requisição |
route | string | rota |
controller | string | nome controlador |
area | string | nome area do controlador |
module | string | nome modulo do controlador |
action | string | nome da ação do modulo |
id | string | identificador da requisição |
Há disponível também, para casos de manipulação específica os dados brutos do ASGI:
Campo | descrição |
---|---|
headers | cabeçalho da requisição |
scope | escopo da requisição |
send | conexão com navegador |
receive | dados recebidos |
Entradas e Pré-condições
Para maior segurança no processamento das rotas é possível e recomendável estabelecer as pré-condições daquela rota específica. Caso a requisição não tenha o objeto ou objeto informado seja inválido, haverá erro de resposta com erro 412: Precondition Failed
@post
async def comment(self, id_product:int, comment:string ) -> Json:
sql_query = f"INSERT INTO products_comments (prod_comment, id_product) VALUES ('{comment}',{id_product})";
.
.
.
await View(self.enviroment, data=[{"added": true}])
Caso a request não contenha os parametros acima, a ação não será executada.
É possível requerer objetos específicos, Bravaweb realiza o cast automático dos dados enviados, no caso datetime o parametro de conversão esta estabelecido no arquivo de configuração nos campos date_format e short_date_format.
from datetime import datetime
from decimal import Decimal
.
@post
async def comment(self, id_product:int, comment:string, date:datetime, stars:Decimal) -> Json:
.
.
.
.
.
.
await View(self.enviroment, data=[{"added": true}])
View
Toda rota deve retornar uma view, que será baseada na anotação a action.
await View(self.enviroment, data=_response_data)
Bravaweb possui tratamento específico para respostas Json e HTML, ambos possuem um modelo ou carregamento de template para resposta.
A View possui os seguintes campos de entrada
entrada | obrigatório | tipo | descrição |
---|---|---|---|
enviorment | sim | bravaweb.enviroment | ambiente da requisição |
data | sim | bytes-like, dict, list, string | dados da resposta de acordo com anotação |
success | não | boolean | sucesso na execução da action |
token | não | string | auth token, caso não informado, havendo token no enviroment, o mesmo se repetirá |
task | não | dict, list, string | dados sobre execução em segundo plano |
error | não | dict, list, string | mensagem de erro |
Anotações e Tipos de Resposta
tipo | Entrada |
---|---|
Html | dict |
Css | bytes-like object |
Csv | bytes-like object |
JavaScript | bytes-like object |
Jpg | bytes-like object |
Json | dict, list, string |
Mp4 | bytes-like object |
bytes-like object | |
Png | bytes-like object |
TextPlain | bytes-like object |
Xml | bytes-like object |
Json
O template Json é composto da seguinte forma:
Json = { "token": "", "success": True, "date": "", "itens": 0, "data": [], }
Onde os dados respondidos estarão dentro de "data".
@get
async def index(self) -> Json:
await View(self.enviroment, data=[{"added": true}])
HTML e Template Mako
Para mais informações sobre a criação de templates Mako acesse: https://www.makotemplates.org/
A estrutura das Views HTML desenvolvidas em Mako devem estar assim:
app
├── ...
├── configuration
├── controllers
├── views
│ └── shared
│ | └── default.html
└── server.py
Quando não há uma view definida para rota, o template padrão a ser carregado será o default.
é possível criar views específicas para cada rota conforme exemplo abaixo:
Rota: /product/detail
@get
async def index(self) -> Html:
await View(self.enviroment, data=[{"added": true}])
Template:
app
├── ...
├── configuration
├── controllers
├── views
│ └── product
│ | └── detail
│ | | └── index.html
│ └── shared
└── server.py
Decoradores
Bravaweb é compatível com encapsulamento através de decorador e a criação deve seguir o modelo abaixo:
Decorador de Método Síncrono:
def decorator_example(f):
def example_decorator(cls, **args) -> f:
return f(cls, **args)
return example_decorator
Decorador de Método Assíncrono:
def decorator_example_async(f):
async def example_decorator(cls, **args) -> f:
return await f(cls, **args)
return example_decorator
O uso do decorador em um método síncrono ficaria assim:
@decorator_example
def __init__(self):
.
.
O uso do decorador em uma rota ficaria assim:
@decorator_example_async
async def index(self) -> Html:
await View(self.enviroment, data=_response_data)
É possível também criar decorar para um controlador inteiro, a função "decora" todos os métodos executáveis, observe que os métodos padrões de classe init e del são métodos síncronos e por isso o decorador síncrono, e demais métodos (actions) com decorador assíncrono.
def decorator_example_klass():
def decorate(cls):
for attr in cls.__dict__:
_method = getattr(cls, attr)
if hasattr(_method, '__call__'):
if attr == "__init__" or attr == "__del__":
setattr(cls, attr, example_decorator(_method))
else:
setattr(cls, attr, decorator_example_async(_method))
return cls
return decorate
Erros:
A qualquer momento no processamento da sua rota é possível responder com as seguintes mensagens de erro:
Metoto | Código de Resposta | Mensagem |
---|---|---|
NoContent | 204 | 204: No Content |
Unauthorized | 401 | 401: Unauthorized |
NotFound | 404 | 404: Not Found |
NotAllowed | 405 | 405: Method not allowed |
PreconditionFailed | 412 | 412: Precondition Failed |
InternalError | 500 | 500: Internal Error |
Exemplo requisição de um arquivo pdf:
import os.path
@get
async def index(self, file_path:str) -> Pdf:
if os.path.exists(file_path):
_file_data = open(file_path,'r')
await View(self.enviroment, data = _file_data.read())
else:
self.NotFound()
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file bravaweb-0.0.12.tar.gz
.
File metadata
- Download URL: bravaweb-0.0.12.tar.gz
- Upload date:
- Size: 25.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.2.0 pkginfo/1.6.1 requests/2.24.0 setuptools/50.3.0 requests-toolbelt/0.9.1 tqdm/4.54.0 CPython/3.8.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8f9aab20dc52cd3f9e8271862e78b9b503f15a6f812684d856d498ddad87c82a |
|
MD5 | 7ebf7cd7fee6356f1fe8a0557fe3bdb6 |
|
BLAKE2b-256 | 0695fdbe7ed4ee42aceda0a289b4861d32a7adef23eba499b6d000aa47f1a108 |