Skip to main content

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
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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bravaweb-0.0.20.tar.gz (24.4 kB view details)

Uploaded Source

File details

Details for the file bravaweb-0.0.20.tar.gz.

File metadata

  • Download URL: bravaweb-0.0.20.tar.gz
  • Upload date:
  • Size: 24.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.9.6

File hashes

Hashes for bravaweb-0.0.20.tar.gz
Algorithm Hash digest
SHA256 e190b52a39af88b3a48bf3eed8991d4efacaf1e66835373047788f3cb37d97c9
MD5 454885d2d4751c13afd90e2d301c539a
BLAKE2b-256 bd913277650c773a7db9bf23581ef87fd6c78847596fe20f13a90879910b9078

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page