bravaweb 0.0.21

Last updated:

0 purchases

bravaweb 0.0.21 Image
bravaweb 0.0.21 Images
Add to Cart

Description:

bravaweb 0.0.21

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


Pdf
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

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.