Metadata-Version: 2.1
Name: bravaweb
Version: 0.0.17
Summary: BravaWeb Framework for ASGI Server
Home-page: https://github.com/robertons/bravaweb
Author: Roberto Neves
Author-email: robertonsilva@gmail.com
License: MIT
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
        ```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
        
Keywords: asgi,wsgi,python3,web,http,framework,mysql,mariadb
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Natural Language :: Portuguese (Brazilian)
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.6
Description-Content-Type: text/markdown
