# 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
```bash
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
```bash
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 |
```python
configuration/__init__.py
# -*- coding: utf-8 -*-
from configuration import api
```
```python
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:
```python
#-*- 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:
```bash\
source ../env/bin/activate
uvicorn server:application --port 8080 --interface=asgi3 --workers 7 --proxy-headers --lifespan off --reload
```
O Resultado então será:
```bash\
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:
```bash
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:
```python
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 é **Default**Controller, 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:
```bash
app
├── ...
├── controllers
│ └── admin
│ │ └── catalog
│ │ | └── products.py
```
O arquivo :
```python
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:
```bash
app
├── ...
├── controllers
│ └── site
│ │ └── product.py
```
O arquivo:
```python
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*
```python
@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*.
```python
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.
```python
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".
```python
@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:
```bash
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**
```python
@get
async def index(self) -> Html:
await View(self.enviroment, data=[{"added": true}])
```
**Template:**
```bash
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:**
```python
def decorator_example(f):
def example_decorator(cls, **args) -> f:
return f(cls, **args)
return example_decorator
```
**Decorador de Método Assíncrono:**
```python
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:**
```python
@decorator_example
def __init__(self):
.
.
```
**O uso do decorador em uma rota ficaria assim:**
```python
@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.**
```python
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:**
```python
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
Raw data
{
"_id": null,
"home_page": "https://github.com/robertons/bravaweb",
"name": "bravaweb",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "asgi,wsgi,python3,web,http,framework,mysql,mariadb",
"author": "Roberto Neves",
"author_email": "robertonsilva@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/73/77/a3b21b43967b2a3466bb74309f81af58c2d6386c42fc2c76239fd1a8b095/bravaweb-0.0.21.tar.gz",
"platform": null,
"description": "# BravaWeb Framework for ASGI Server\n\nFramework para aplica\u00e7\u00f5es WEB baseada em Python3 ASGI (_Asynchronous Server Gateway Interfac_ em Uvicorn), com possibilidade de utiliza\u00e7\u00e3o de Template em Html (Mako Templates).\n\nVeja Documenta\u00e7\u00e3o em:\n\nUvicorn: https://www.uvicorn.org/\nMako Templates: https://www.makotemplates.org/\n\n# Instala\u00e7\u00e3o\nInstala\u00e7\u00e3o utilizando Pip\n```bash\npip install bravaweb\n```\nGit/Clone\n```\ngit clone https://github.com/robertons/bravaweb\ncd bravaweb\npip install -r requirements.txt\npython setup.py install\n```\n\n# Primeiros Passos\n\nInicie seu projeto conforme estrutura abaixo\n\n\n```bash\napp\n\u251c\u2500\u2500 ...\n\u251c\u2500\u2500 configuration \t\t\n\u2502 \u251c\u2500\u2500 __init__.py \n\u2502 \u2514\u2500\u2500 api.py \n\u2514\u2500\u2500 server.py\n```\n\nO arquivo de configura\u00e7\u00f5es deve conter os seguintes dados:\n\n| vari\u00e1vel \t\t | tipo | obrigat\u00f3rio | descri\u00e7\u00e3o \t\t\t |\n|-------------------|-------------|-------------|-------------------------------|\n| directory \t| string | sim | Caminho Projeto \t\t \t|\n| encoding \t\t \t | string | sim | Codifica\u00e7\u00e3o \t\t\t\t |\n| date_format \t| string | sim | Formato data \t\t |\n| short_date_format | string | sim | Formato data curta |\n| token \t\t\t | string | sim | Token codifica\u00e7\u00e3o Authorization Header |\n| domains \t\t | array | sim | Dom\u00ednios autorizados a acessar|\n| access_exceptions | array | sim | Rotas e Exce\u00e7\u00f5es de acesso \t|\n| routes \t\t\t | array | sim | Rotas do Projeto \t\t |\n\n\n```python\n\nconfiguration/__init__.py\n\n# -*- coding: utf-8 -*-\n\nfrom configuration import api\n\n```\n\n\n```python\n\nconfiguration/api.py\n\n# -*- coding: utf-8 -*-\n\nimport os\n\n# Directory\ndirectory = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))\n\n# Api Encoding\nencoding = \"utf-8\"\n\n# Date Format\ndate_format = \"%d/%m/%Y %H:%M:%S\"\nshort_date_format = \"%d/%m/%Y\"\n\n# Token Authorization\ntoken = \"JWT-Token-Project\"\n\n# Authorized Domains Origin/Referrer\ndomains = [\n \"https://www.dominio.com.br\",\n \"https://alias.dominio.com.br\",\n]\n\n# Exceptions Routes\naccess_exceptions = [\n\n {'path': '(^/default/)','referer': '*'},\n\n {'path': '(/rota/especifica/)','referer': '(^https://dominio.especifico.com.br/)'},\n\n {'path': '*', 'referer': \"(^https://outro.dominio.com.br/)|(^https://adicional.dominio.com.br/)\"},\n]\n\nroutes = [\n (\"{controller}/{area}/{module}/{action}/{id}\", '(^/admin/)|(^/panel/)',\n (\"{controller}/{module}/{action}/{id}\", \"\"),\n]\n\n```\n\n**Defini\u00e7\u00f5es:**\n\n**domains**: lista array de strings, com dom\u00ednios que tem acesso a api, o teste \u00e9 feito baseado no origin e/ou referrer de cada requisi\u00e7\u00e3o.\n\n**access_exceptions**: \u00e9 possivel que algumas rotas sejam abertas para qualquer requisi\u00e7\u00e3o, ou mesmo que alguma rota seja especifica para algum dom\u00ednio. A lista deve conter um dicion\u00e1rio com as chaves path e referer onde:\n\n\tpath: \u00e9 referente ao caminho da rota\n\treferer: origem da requisi\u00e7\u00e3o\n\nAmbos os valores aceitam * para todos ou express\u00e3o regular para teste de string.\n\n**routes**: lista com tuplas que definem as rotas padr\u00f5es do projeto. Bravaweb esta preparado para at\u00e9 4 n\u00edveis de profundidade que definem, Controlador, Area, Modulo, A\u00e7\u00e3o e mais um n\u00edvel **opcional** para capta\u00e7\u00e3o de ID, a prioridade das regras \u00e9 sequencial, portanto as regras espec\u00edficas devem vir primeiro. A tupla \u00e9 definida assim:\n\n\t0: a capta\u00e7\u00e3o de cada parte da profundidade para carregamento\n\t1: express\u00e3o regular para identificar a regra\n\nPor padr\u00e3o os valores de rota do ambiente s\u00e3o:\n\n controller = None\n area = None\n module = \"default\"\n action=\"index\"\n id = None\n\nPor fim vamos criar a execu\u00e7\u00e3o do projeto que vai tratar as requisi\u00e7\u00f5es e processar as rotas.\n\nO arquivo `server.py` na raiz deve ficar assim:\n\n```python\n#-*- coding: utf-8 -*-\nimport sys\n\nimport configuration\n\nfrom bravaweb import App as application\n\n```\n\nAcesse o diret\u00f3rio do seu projeto, e execute o comando de servi\u00e7o do ASGI, conforme documenta\u00e7\u00e3o do Uvicorn, no exemplo abaixo ativamos o ambiente virtual onde os pacotes est\u00e3o instalados:\n\n\n```bash\\\nsource ../env/bin/activate\n\nuvicorn server:application --port 8080 --interface=asgi3 --workers 7 --proxy-headers --lifespan off --reload\n\n```\nO Resultado ent\u00e3o ser\u00e1:\n\n```bash\\\nINFO: Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit)\nINFO: Started reloader process [82503] using statreload\nINFO: Started server process [82505]\n.\n.\n.\n```\n\nNeste momento sua aplica\u00e7\u00e3o estar\u00e1 em execu\u00e7\u00e3o. N\u00f3s configuramos as rotas mas n\u00e3o desenvolvemos nenhuma delas portanto qualquer requisi\u00e7\u00e3o na url http://127.0.0.1:8080 ir\u00e1 retornar 404.\n\n# Hello World\n\n\nVamos iniciar aplicando a rota default, a pasta do projeto nesse momento dever\u00e1 estar assim:\n```bash\napp\n\u251c\u2500\u2500 ...\n\u251c\u2500\u2500 configuration \t\t\n\u2502 \u251c\u2500\u2500 __init__.py \n\u2502 \u2514\u2500\u2500 api.py \n\u251c\u2500\u2500 controllers\n\u2502 \u2514\u2500\u2500 default.py \n\u2514\u2500\u2500 server.py\n```\nConforme exemplificado a rota default(padr\u00e3o) \u00e9\n\n controller = None\n area = None\n module = \"default\"\n action=\"index\"\n id = None\n\n\nO arquivo ficar\u00e1 assim:\n\n```python\n\ncontrollers/default.py\n\n# -*- coding: utf-8 -*-\nfrom bravaweb.controller import *\n\nclass DefaultController(Controller):\n\n @get\n async def index(self) -> Json:\n await View(self.enviroment, data={\"mensagem\": 'Ol\u00e1 Mundo'})\n\n```\n\n**Analisando a rota default:**\n\nNome do Controlador \u00e9 default, por isso nome da classe \u00e9 **Default**Controller, herdando o controlador do framework (Controller)\n\nO metodo de request aceito para esta rota \u00e9 o GET (*@get*) , mas POST (*@post*) , PUT(*@put*) e DELETE(*@delete*) tamb\u00e9m s\u00e3o aceitos. Uma requisi\u00e7\u00e3o diferente do permitido para rota retorna Erro *405: Method not allowed*\n\nO Framwork \u00e9 baseado em ASGI (_Asynchronous Server Gateway Interface_) por isso a\u00e7\u00e3o index \u00e9 ass\u00edncrona (async) .\n\nA anota\u00e7\u00e3o \u00e9 o tipo de resultado que essa rota ir\u00e1 retornar, posteriormente veremos sobre os tipos, no exemplo acima utilizamos Json.\n\nTodos os dados da requisi\u00e7\u00e3o, est\u00e3o na *enviroment*, veremos mais logo a seguir.\n\n\nPara melhor compreen\u00e7\u00e3o sobre as rotas , vejamos os exemplos abaixo baseado no arquivo de configura\u00e7\u00e3o acima:\n\n# Criando e Configurando Rotas\n\nOs padr\u00f5es de rota \u00e9 configurado no arquivo de configura\u00e7\u00f5es em routes. Voc\u00ea provavelmente far\u00e1 isso somente uma vez, ou quando for necess\u00e1ria a cria\u00e7\u00e3o de rotas espec\u00edficas em seu projeto. Abaixo segue alguns exemplos baseado na configura\u00e7\u00e3o que apresentamos.\n\n## Exemplo 1\n\n GET -> api.dominio.com.br/admin/catalog/products/list\n\nA regra identificada \u00e9 a primeira da lista, pois o path da request inicia com */admin/* conforme express\u00e3o regular da posi\u00e7\u00e3o [1] da tupla em *configuration.api.routes*:\n\n (\"{controller}/{area}/{module}/{action}/{id}\", '(^/admin/)|(^/panel/)'\n\nO resultado da capta\u00e7\u00e3o da rota conforme posi\u00e7\u00e3o [0] da tupla ser\u00e1:\n\n controller = 'admin'\n area = 'catalog'\n module = 'products'\n action = 'list'\n\nA estrutura para processamento desta rota devera ser:\n\n```bash\napp\n\u251c\u2500\u2500 ... \n\u251c\u2500\u2500 controllers\n\u2502 \u2514\u2500\u2500 admin\n\u2502 \u2502\t\u2514\u2500\u2500 catalog\n\u2502 \u2502\t|\t\u2514\u2500\u2500 products.py \n```\n\nO arquivo :\n\n```python\n\ncontrollers/admin/catalog/products.py\n\n# -*- coding: utf-8 -*-\nfrom bravaweb.controller import *\n\nclass ProductsController(Controller):\n\n @get\n async def list(self) -> Json:\n await View(self.enviroment, data=[{\"prod_nome\": 'Exemplo'}])\n\n```\n\n## Exemplo 2\n\n POST -> api.dominio.com.br/site/product/like/110\n\nA regra identificada \u00e9 a default (segunda da lista), pois o path da request **n\u00e3o** contempla as express\u00f5es regulares anteriores :\n\n (\"{controller}/{module}/{action}/{id}\", \"\")\n\nO resultado da capta\u00e7\u00e3o da rota conforme posi\u00e7\u00e3o [0] da tupla ser\u00e1:\n\n controller = 'site'\n area = None\n module = 'product'\n action = 'like'\n id = 110\n\nA estrutura para processamento desta rota devera ser:\n\n```bash\napp\n\u251c\u2500\u2500 ... \n\u251c\u2500\u2500 controllers\n\u2502 \u2514\u2500\u2500 site\n\u2502 \u2502\t\u2514\u2500\u2500 product.py \n```\n\nO arquivo:\n\n```python\n\ncontrollers/site/products.py\n\n# -*- coding: utf-8 -*-\nfrom bravaweb.controller import *\n\nclass ProductController(Controller):\n\n @post\n async def like(self) -> Json:\n await View(self.enviroment, data=[{\"likes\": 535}])\n\n```\n\n# Ambiente / Enviroment\n\nA qualquer momento dentro do controlador \u00e9 possivel acessar os dados da requisi\u00e7\u00e3o atrav\u00e9s de *self.enviroment* os dados dispon\u00edves s\u00e3o:\n\n|Campo | Tipo | descri\u00e7\u00e3o |\n|--|--|--|\n| origin | string | Origem ou Referrer da Requisi\u00e7\u00e3o|\n| remote_ip | string | Ip do usu\u00e1rio|\n| remote_uuid | string | UUID se informado no header |\n| browser | string | Browser do usu\u00e1rio |\n| accept_encoding | string | tipos de codifica\u00e7\u00e3o aceito pelo browser |\n| method | string | metodo da requisi\u00e7\u00e3o (GET, POST, PUT ou DELETE) |\n| response_type| string | tipo de resposta esperada para requisi\u00e7\u00e3o|\n| authorization| string | Token JWT - Bearer enviado no Header|\n| bearer| string | Token JWT decodificado |\n| content_length| int | Tamanho da requisi\u00e7\u00e3o |\n| get| dict | Dados enviados por querystring |\n| post| dict | Dados enviados por post |\n| body | bytes | Bytes do corpo da requisi\u00e7\u00e3o |\n| route | string | rota |\n| controller | string | nome controlador |\n| area | string | nome area do controlador |\n| module | string | nome modulo do controlador |\n| action | string | nome da a\u00e7\u00e3o do modulo |\n| id | string | identificador da requisi\u00e7\u00e3o |\n\nH\u00e1 dispon\u00edvel tamb\u00e9m, para casos de manipula\u00e7\u00e3o espec\u00edfica os dados brutos do ASGI:\n\n|Campo | descri\u00e7\u00e3o |\n|--|--|\n| headers | cabe\u00e7alho da requisi\u00e7\u00e3o |\n| scope | escopo da requisi\u00e7\u00e3o |\n| send | conex\u00e3o com navegador |\n| receive | dados recebidos |\n\n# Entradas e Pr\u00e9-condi\u00e7\u00f5es\n\nPara maior seguran\u00e7a no processamento das rotas \u00e9 poss\u00edvel e **recomend\u00e1vel** estabelecer as pr\u00e9-condi\u00e7\u00f5es daquela rota espec\u00edfica. Caso a requisi\u00e7\u00e3o n\u00e3o tenha o objeto ou objeto informado seja inv\u00e1lido, haver\u00e1 erro de resposta com erro *412: Precondition Failed*\n\n```python\n @post\n async def comment(self, id_product:int, comment:string ) -> Json:\n\t sql_query = f\"INSERT INTO products_comments (prod_comment, id_product) VALUES ('{comment}',{id_product})\";\n\t .\n\t\t.\n\t\t.\n await View(self.enviroment, data=[{\"added\": true}])\n\n```\n\nCaso a request n\u00e3o contenha os parametros acima, a a\u00e7\u00e3o n\u00e3o ser\u00e1 executada.\n\n\u00c9 poss\u00edvel requerer objetos espec\u00edficos, Bravaweb realiza o cast autom\u00e1tico dos dados enviados, no caso datetime o parametro de convers\u00e3o esta estabelecido no arquivo de configura\u00e7\u00e3o nos campos *date_format* e *short_date_format*.\n\n```python\nfrom datetime import datetime\nfrom decimal import Decimal\n.\n\n @post\n async def comment(self, id_product:int, comment:string, date:datetime, stars:Decimal) -> Json:\n\t .\n\t\t.\n\t\t.\n\t .\n\t\t.\n\t\t.\n await View(self.enviroment, data=[{\"added\": true}])\n\n```\n\n# View\n\nToda rota deve retornar uma view, que ser\u00e1 baseada na anota\u00e7\u00e3o a action.\n```python\n await View(self.enviroment, data=_response_data)\n```\n\nBravaweb possui tratamento espec\u00edfico para respostas Json e HTML, ambos possuem um modelo ou carregamento de template para resposta.\n\nA View possui os seguintes campos de entrada\n\n| entrada | obrigat\u00f3rio | tipo | descri\u00e7\u00e3o\n|--|--|--|--|\n| enviorment | sim | bravaweb.enviroment | ambiente da requisi\u00e7\u00e3o\n| data | sim | bytes-like, dict, list, string | dados da resposta de acordo com anota\u00e7\u00e3o\n| success | n\u00e3o | boolean | sucesso na execu\u00e7\u00e3o da action\n| token | n\u00e3o | string | auth token, caso n\u00e3o informado, havendo token no enviroment, o mesmo se repetir\u00e1\n| task | n\u00e3o | dict, list, string | dados sobre execu\u00e7\u00e3o em segundo plano\n| error | n\u00e3o | dict, list, string | mensagem de erro\n\n\n# Anota\u00e7\u00f5es e Tipos de Resposta\n\n\n|tipo | Entrada |\n|--|--|\n| Html | dict |\n| Css | bytes-like object |\n| Csv | bytes-like object |\n| JavaScript | bytes-like object |\n| Jpg | bytes-like object |\n| Json | dict, list, string |\n| Mp4 |bytes-like object |\n| Pdf | bytes-like object |\n| Png | bytes-like object|\n| TextPlain | bytes-like object |\n| Xml | bytes-like object |\n\n\n# Json\nO template Json \u00e9 composto da seguinte forma:\n\nJson = {\n\t \"token\": \"\",\n\t \"success\": True,\n\t \"date\": \"\",\n\t \"itens\": 0,\n\t \"data\": [],\n}\n\nOnde os dados respondidos estar\u00e3o dentro de \"data\".\n```python\n @get\n async def index(self) -> Json:\n await View(self.enviroment, data=[{\"added\": true}])\n```\n\n\n# HTML e Template Mako\n\nPara mais informa\u00e7\u00f5es sobre a cria\u00e7\u00e3o de templates Mako acesse: https://www.makotemplates.org/\n\nA estrutura das Views HTML desenvolvidas em Mako devem estar assim:\n\n```bash\napp\n\u251c\u2500\u2500 ...\n\u251c\u2500\u2500 configuration \t\t \n\u251c\u2500\u2500 controllers\n\u251c\u2500\u2500 views\n\u2502 \u2514\u2500\u2500 shared \n\u2502 |\t\u2514\u2500\u2500 default.html \n\u2514\u2500\u2500 server.py\n```\n\nQuando n\u00e3o h\u00e1 uma view definida para rota, o template padr\u00e3o a ser carregado ser\u00e1 o default.\n\n\u00e9 poss\u00edvel criar views espec\u00edficas para cada rota conforme exemplo abaixo:\n\nRota: **/product/detail**\n\n```python\n @get\n async def index(self) -> Html:\n await View(self.enviroment, data=[{\"added\": true}])\n```\n\n**Template:**\n\n```bash\napp\n\u251c\u2500\u2500 ...\n\u251c\u2500\u2500 configuration \t\t \n\u251c\u2500\u2500 controllers\n\u251c\u2500\u2500 views\n\u2502 \u2514\u2500\u2500 product \n\u2502 |\t\u2514\u2500\u2500 detail\n\u2502 |\t|\t\u2514\u2500\u2500 index.html \n\u2502 \u2514\u2500\u2500 shared \n\u2514\u2500\u2500 server.py\n```\n\n\n# Decoradores\n\nBravaweb \u00e9 compat\u00edvel com encapsulamento atrav\u00e9s de decorador e a cria\u00e7\u00e3o deve seguir o modelo abaixo:\n\n**Decorador de M\u00e9todo S\u00edncrono:**\n\n```python\ndef decorator_example(f):\n def example_decorator(cls, **args) -> f:\n return f(cls, **args)\n return example_decorator\n```\n\n**Decorador de M\u00e9todo Ass\u00edncrono:**\n\n```python\ndef decorator_example_async(f):\n async def example_decorator(cls, **args) -> f:\n return await f(cls, **args)\n return example_decorator\n```\n\n**O uso do decorador em um m\u00e9todo s\u00edncrono ficaria assim:**\n\n```python\n @decorator_example\n def __init__(self):\n .\n .\n```\n\n**O uso do decorador em uma rota ficaria assim:**\n\n```python\n @decorator_example_async\n async def index(self) -> Html:\n await View(self.enviroment, data=_response_data)\n```\n\n\n**\u00c9 poss\u00edvel tamb\u00e9m criar decorar para um controlador inteiro, a fun\u00e7\u00e3o \"decora\" todos os m\u00e9todos execut\u00e1veis, observe que os m\u00e9todos padr\u00f5es de classe __init__ e __del__ s\u00e3o m\u00e9todos s\u00edncronos e por isso o decorador s\u00edncrono, e demais m\u00e9todos (actions) com decorador ass\u00edncrono.**\n\n```python\ndef decorator_example_klass():\n def decorate(cls):\n for attr in cls.__dict__:\n _method = getattr(cls, attr)\n if hasattr(_method, '__call__'):\n if attr == \"__init__\" or attr == \"__del__\":\n setattr(cls, attr, example_decorator(_method))\n else:\n setattr(cls, attr, decorator_example_async(_method))\n return cls\n return decorate\n```\n\n# Erros:\n\nA qualquer momento no processamento da sua rota \u00e9 poss\u00edvel responder com as seguintes mensagens de erro:\n\n| Metoto | C\u00f3digo de Resposta | Mensagem |\n|--|--|--|\n| NoContent | 204 | 204: No Content\n| Unauthorized | 401 | 401: Unauthorized\n| NotFound | 404 | 404: Not Found\n| NotAllowed | 405 | 405: Method not allowed\n| PreconditionFailed | 412 | 412: Precondition Failed\n| InternalError | 500 | 500: Internal Error\n\n**Exemplo requisi\u00e7\u00e3o de um arquivo pdf:**\n\n```python\n\nimport os.path\n\n @get\n async def index(self, file_path:str) -> Pdf:\n\t if os.path.exists(file_path):\n\t\t\t_file_data = open(file_path,'r')\n\t await View(self.enviroment, data = _file_data.read())\n\t else:\n\t\t self.NotFound()\n```\n\n\n## License\n\nMIT\n\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "BravaWeb Framework for ASGI Server",
"version": "0.0.21",
"project_urls": {
"Homepage": "https://github.com/robertons/bravaweb"
},
"split_keywords": [
"asgi",
"wsgi",
"python3",
"web",
"http",
"framework",
"mysql",
"mariadb"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "7377a3b21b43967b2a3466bb74309f81af58c2d6386c42fc2c76239fd1a8b095",
"md5": "268e0733bb56ed3bce4678f229bbe626",
"sha256": "d6df005ab8eaac322c873630ba03ca8c4c7f890c7c6f99bc30b85bdc9a419bc8"
},
"downloads": -1,
"filename": "bravaweb-0.0.21.tar.gz",
"has_sig": false,
"md5_digest": "268e0733bb56ed3bce4678f229bbe626",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 24456,
"upload_time": "2023-12-05T19:03:41",
"upload_time_iso_8601": "2023-12-05T19:03:41.375482Z",
"url": "https://files.pythonhosted.org/packages/73/77/a3b21b43967b2a3466bb74309f81af58c2d6386c42fc2c76239fd1a8b095/bravaweb-0.0.21.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-12-05 19:03:41",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "robertons",
"github_project": "bravaweb",
"github_not_found": true,
"lcname": "bravaweb"
}