Package: bravaweb
BravaWeb Framework for ASGI ServerFramework para aplicações WEB baseada em Python3 ASGI  (Asynchronous Server Gateway Interfacem  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çãoInstalação utilizando PippipinstallbravawebGit/Clonegit clone https://github.com/robertons/bravaweb
cd bravaweb
pip install -r requirements.txt
python setup.py installPrimeiros PassosInicie seu projeto conforme estrutura abaixoapp
├──...
├──configuration│├──__init__.py│└──api.py└──server.pyO arquivo de configurações deve conter os seguintes dados:variáveltipoobrigatóriodescriçãodirectorystringsimCaminho ProjetoencodingstringsimCodificaçãodate_formatstringsimFormato datashort_date_formatstringsimFormato data curtatokenstringsimToken codificação Authorization HeaderdomainsarraysimDomínios autorizados a acessaraccess_exceptionsarraysimRotas e Exceções de acessoroutesarraysimRotas do Projetoconfiguration/__init__.py# -*- coding: utf-8 -*-fromconfigurationimportapiconfiguration/api.py# -*- coding: utf-8 -*-importos# Directorydirectory=os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)),os.pardir))# Api Encodingencoding="utf-8"# Date Formatdate_format="%d/%m/%Y %H:%M:%S"short_date_format="%d/%m/%Y"# Token Authorizationtoken="JWT-Token-Project"# Authorized Domains Origin/Referrerdomains=["https://www.dominio.com.br","https://alias.dominio.com.br",]# Exceptions Routesaccess_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çãoAmbos 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ívelopcionalpara 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 regraPor padrão os valores de rota do ambiente são:controller = None
area = None
module = "default"
action="index"
id = NonePor fim vamos  criar a execução do projeto que vai tratar as requisições e processar as rotas.O arquivoserver.pyna raiz deve ficar assim:#-*- coding: utf-8 -*-importsysimportconfigurationfrombravawebimportAppasapplicationAcesse 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 --reloadO 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 urlhttp://127.0.0.1:8080irá retornar 404.Hello WorldVamos iniciar aplicando a rota default, a pasta do projeto nesse momento deverá estar assim:app
├──...
├──configuration│├──__init__.py│└──api.py├──controllers
│└──default.py└──server.pyConforme exemplificado a rota default(padrão) écontroller = None
area = None
module = "default"
action="index"
id = NoneO arquivo ficará assim:controllers/default.py# -*- coding: utf-8 -*-frombravaweb.controllerimport*classDefaultController(Controller):@getasyncdefindex(self)->Json:awaitView(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 Erro405: Method not allowedO 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 naenviroment, 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 RotasOs 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 1GET -> api.dominio.com.br/admin/catalog/products/listA regra identificada é a primeira da lista, pois o path da request inicia com/admin/conforme expressão regular da posição [1]  da tupla emconfiguration.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.pyO arquivo :controllers/admin/catalog/products.py# -*- coding: utf-8 -*-frombravaweb.controllerimport*classProductsController(Controller):@getasyncdeflist(self)->Json:awaitView(self.enviroment,data=[{"prod_nome":'Exemplo'}])Exemplo 2POST -> api.dominio.com.br/site/product/like/110A regra identificada é a default (segunda da lista), pois o path da requestnãocontempla 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 = 110A estrutura para processamento desta rota devera ser:app
├──...├──controllers
│└──site
││└──product.pyO arquivo:controllers/site/products.py# -*- coding: utf-8 -*-frombravaweb.controllerimport*classProductController(Controller):@postasyncdeflike(self)->Json:awaitView(self.enviroment,data=[{"likes":535}])Ambiente / EnviromentA qualquer momento dentro do controlador é possivel acessar  os dados da requisição através deself.enviromentos dados disponíves são:CampoTipodescriçãooriginstringOrigem ou Referrer da Requisiçãoremote_ipstringIp do usuárioremote_uuidstringUUID se informado no headerbrowserstringBrowser do usuárioaccept_encodingstringtipos de codificação aceito pelo browsermethodstringmetodo da requisição (GET, POST, PUT ou DELETE)response_typestringtipo de resposta esperada para requisiçãoauthorizationstringToken JWT - Bearer enviado no HeaderbearerstringToken JWT decodificadocontent_lengthintTamanho da requisiçãogetdictDados enviados por querystringpostdictDados enviados por postbodybytesBytes do corpo da requisiçãoroutestringrotacontrollerstringnome controladorareastringnome area do controladormodulestringnome modulo do controladoractionstringnome da ação do moduloidstringidentificador da requisiçãoHá disponível também, para casos de manipulação específica os dados brutos do ASGI:Campodescriçãoheaderscabeçalho da requisiçãoscopeescopo da requisiçãosendconexão com navegadorreceivedados recebidosEntradas e Pré-condiçõesPara maior segurança no processamento das rotas é possível erecomendávelestabelecer 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 erro412: Precondition Failed@postasyncdefcomment(self,id_product:int,comment:string)->Json:sql_query=f"INSERT  INTO products_comments (prod_comment, id_product) VALUES ('{comment}',{id_product})";...awaitView(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 camposdate_formateshort_date_format.fromdatetimeimportdatetimefromdecimalimportDecimal.@postasyncdefcomment(self,id_product:int,comment:string,date:datetime,stars:Decimal)->Json:......awaitView(self.enviroment,data=[{"added":true}])ViewToda rota deve retornar uma view, que será baseada na anotação a action.awaitView(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 entradaentradaobrigatóriotipodescriçãoenviormentsimbravaweb.enviromentambiente da requisiçãodatasimbytes-like, dict, list, stringdados da resposta de acordo com anotaçãosuccessnãobooleansucesso na execução da actiontokennãostringauth token, caso não informado, havendo token no enviroment, o mesmo se repetirátasknãodict, list, stringdados sobre execução em segundo planoerrornãodict, list, stringmensagem de erroAnotações e Tipos de RespostatipoEntradaHtmldictCssbytes-like objectCsvbytes-like objectJavaScriptbytes-like objectJpgbytes-like objectJsondict, list, stringMp4bytes-like objectPdfbytes-like objectPngbytes-like objectTextPlainbytes-like objectXmlbytes-like objectJsonO template Json é composto da seguinte forma:Json = {
"token": "",
"success": True,
"date": "",
"itens": 0,
"data": [],
}Onde os dados respondidos estarão dentro de "data".@getasyncdefindex(self)->Json:awaitView(self.enviroment,data=[{"added":true}])HTML e Template MakoPara 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.pyQuando 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@getasyncdefindex(self)->Html:awaitView(self.enviroment,data=[{"added":true}])Template:app
├──...
├──configuration├──controllers
├──views
│└──product│|└──detail
│||└──index.html│└──shared└──server.pyDecoradoresBravaweb é compatível com encapsulamento através de decorador e a criação deve seguir o modelo abaixo:Decorador de Método Síncrono:defdecorator_example(f):defexample_decorator(cls,**args)->f:returnf(cls,**args)returnexample_decoratorDecorador de Método Assíncrono:defdecorator_example_async(f):asyncdefexample_decorator(cls,**args)->f:returnawaitf(cls,**args)returnexample_decoratorO uso do decorador em um método síncrono  ficaria assim:@decorator_exampledef__init__(self):..O uso do decorador em uma rota ficaria assim:@decorator_example_asyncasyncdefindex(self)->Html:awaitView(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 classeinitedelsão métodos síncronos e por isso o decorador síncrono, e demais métodos (actions) com decorador assíncrono.defdecorator_example_klass():defdecorate(cls):forattrincls.__dict__:_method=getattr(cls,attr)ifhasattr(_method,'__call__'):ifattr=="__init__"orattr=="__del__":setattr(cls,attr,example_decorator(_method))else:setattr(cls,attr,decorator_example_async(_method))returnclsreturndecorateErros:A qualquer momento no processamento da sua rota é possível responder  com as seguintes mensagens de erro:MetotoCódigo de RespostaMensagemNoContent204204: No ContentUnauthorized401401: UnauthorizedNotFound404404: Not FoundNotAllowed405405: Method not allowedPreconditionFailed412412: Precondition FailedInternalError500500: Internal ErrorExemplo requisição de um arquivo pdf:importos.path@getasyncdefindex(self,file_path:str)->Pdf:ifos.path.exists(file_path):_file_data=open(file_path,'r')awaitView(self.enviroment,data=_file_data.read())else:self.NotFound()LicenseMIT