Skip to content
🍬Async web framework
Python
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
demos
docs
nougat
tests
.gitignore
.travis.yml
CHANGELOG.md
LICENSE
Pipfile
Pipfile.lock
readme.md
setup.py

readme.md

Nougat

基于 Python 3.6 的异步框架。Nougat 使用中间件的形式来处理逻辑。

PyPI PyPI PyPI PyPI Build Status

安装

Nougat 目前仅支持 Python3.6,所以你可以通过 PYPI 来安装:

pip3 install nougat  # or pip install nougat

或者你可以通过 pipenv 来安装(推荐):

pipenv install nougat

开发版本

如果你想使用 Nougat 的新特性,可以选择使用开发版本。注意这同时也意味着框架会存在着更多的BUG

pip3 install git@github.com:Kilerd/nougat.git@develop

Uvloop 支持

Nougat 依赖于 asyncio,所以如果你打算把 Nougat 运行在 Linux 上,可以选择使用更好的异步库来运行

首先安装 uvloop:

pip3 install uvloop

然后在项目中添加以下代码以使用 uvloop:

import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

快速上手

from nougat import Nougat

app = Nougat()

async def middleware(response):

    response.content = 'Hello world'

app.use(middleware)
app.run()

上述代码描述了 Nougat 的最基本使用过程:

  • 引用 Nougat
  • 定义中间件
  • 把中间件倒入到 Nougat
  • 运行 Nougat

Nougat 类

框架中最重要的便是 Nougat 类。它储存了所有注册的中间件和信号处理函数,同时用于启动和停止服务器

app = Nougat()

在 Nougat 类中,最重要的便是注册中间件和添加信号处理函数

注册中间件 app.use(middleware...)

use 方法可以同时传入多个中间件,注册顺序跟传入顺序一致

async def one_middleware():
    pass

async def another_middleware(response):
    response.content = 'Hello World'
   
app.use(one_middleware)
app.use(one_middleware, another_middleware)

约定的中间件必须是异步函数,同时可选的参数有四个:app, request, response, next 。参数都是可选的,可根据自己的需求选择

具体的中间件定义和执行流程在下文会继续描述

配置

Nougat 类中的 config 变量用于管理配置信息

从 Python 对象中读取配置信息

app.config 中有一个方法load_from_object(object_name) 用于读取指定对象中所有的大写字母组成的变量

class DefaultConfig:
    UPPER_WORD = 1
    normal = 2

app.config.load_from_object(DefaultConfig)

在这个例子中,UPPER_WORD会被加载进配置app.config中,但是normal 不会

访问配置信息

app.config 中的信息可以通过 object 的方式访问,也可以通过dict的方式访问:

  • 对象访问方式: app.config.UPPER_WORD
  • 字典访问方式: app.config['UPPER_WORD']

我们推荐使用对象访问方式,因为 Nougat 在处理不存在的信息时,会返回None,而不会报错

如果你执意要用字典访问方式,请使用app.config.get("UPPER_WORD", None)来避免触发 KeyError 异常

中间件

在 Nougat 中,中间件必须是一个异步函数(或者在类中实现async def __call__ 方法),同时参数只能先定在app, request, response, next 里面,按需定义

async def middleware(app, request, response, next):
    # doing before request
    await next()
    # doing after after
  • appNougat 的实例
  • requestRequest 实例,当前请求上下文的请求对象
  • responseResponse实例,当前请求上下文的响应对象
  • next 为中间件链的下一个中间件,经由包装后的无参数方法,可直接await next() 调用

注意,为了可以得到更好的代码提示,建议在编写中间件参数时,使用 Type Hints。 async def middleware(app: Nougat, request: Request, response: Response, next)

async def middleware(app, request, next):
    if request.headers.get('Authentication'):
        print('get user token')
    await next()

app.use(middleware)

上述中间件使用了三个参数,并没有使用到response

或者,我们可以编写一个类作为中间件

class Middleware:
    
    async def __call__(next): 
        await next()

app.use(Middleware())

中间件执行顺序



                      Request
              +                 ^
              |                 |
       +------|-----------------|-------+
       |      |                 |       |
       |      |   middleware 1  |       |
       |      |                 |       |
       +------|-----------------|-------+
              |                 |
       +------|-----------------|-------+
       |      |                 |       |
       |      |   middleware 2  |       |
       |      |                 |       |
       +------|-----------------|-------+
              |                 |
              |                 |
              +-----------------+

await next() 把一个中间件分成两个部分(当找不到该语句时,整个中间件默认被理解为上半部分),Nougat 在处理中间件时,会正向处理中间件链的上半部分,逆向处理下半部分

v = []
async def m1(next):
    v.append(1)
    await next()
    v.append(2)
    
async def m2(next):
    v.append(3)
    await next()
    v.append(4)
    
app.use(m1, m2)

当经历过一次HTTP请求后,v的内容为[1, 3, 4, 2]

记录处理时间的例子

async def logging_time(response, next):
    start_time = time()
    await next()
    process_time = time() - start_time
    response.set_header("Time": "{}s".format(process_time))

信号

信号用于 Nougat 服务器启动前后处理的事情,例如建立数据库连接,数据的预处理等等

目前,Nougat 只支持两种信号,启动服务器前信号 before_start 和 关闭服务器后信号after_start

@app.signal('before_start')
async def handle(app):
    pass

使用 app.signal 装饰器可以完成该工作,参数为信号值。 装饰器函数可以为异步函数,也可以为普通函数。参数为 app,其类型为 Nougat 类的实例

信号的具体应用请自行想象

高级使用说明

请求类 Request

Request 类用于储存 HTTP 请求的相关信息

request.method

HTTP 访问方法,为大写字符串 GET, POST

request.version

HTTP 协议版本

request.url

HTTP 请求的URL,为 yarl.URL 实例,以下是一些操作实例

url = yarl.URL("https://example.com/foo/bar?hello=world")
print(url.host)  # example.com
print(url.path)  # /foo/bar
print(url.query)  # <MultiDictProxy('hello': 'world')>
print(url.query.get('hello'))  # world

request.content_type

HTTP 的 Content-Type

request.query

request.url.query 的快捷访问方式

request.form

HTTP 非 GET 方法中 FORM 数据储存。

request.ip

HTTP 访问者的 IP

request.headers

HTTP 请求头部信息

request.cookies

HTTP 请求的 Cookies

响应类 Response

response.code =

响应的HTTP CODE, 默认为 200

response.status =

对应HTTP CODE的文本信息,200 对应 OK,默认会从表中查询出默认值。

response.content =

响应内容

response.type =

响应内容的类型,默认为text/html

response.charset =

响应内容的编码,默认为 utf-8

response.set_header(key, value)

添加响应头

response.set_cookies(self, name, value, expires=None, domain=None, path=None, secure=False, http_only=False, same_site=None)

添加 Cookie

  • name: 名字
  • value: 值
  • expires: 过期时间,单位秒

用HttpException中断中间件链

在中间件处理业务逻辑时,在某些情况下,我们希望提前中断中间件链的执行,例如当用户认证失败时,直接返回 HTTP 401 Unauthorized。 Nougat 提供了nougat.exceptions.HttpException 来实现这个功能

async def login_required(request, next):
    if request.headers.get('Authentication'):
        ...
    if not is_auth:
        raise HttpException(401, 'you need to login first')
    ...        

HttpException 接受两个参数:

  • code HTTP CODE
  • body HTTP 响应内容

如何写单元测试

Nougat 提供了一个非常方便的库,用于执行单元测试。当然了,我们依赖于pytestpytest-asyncio 来提供基础配件

import pytest
from app import app
from nougat import TestClient

@pytest.mark.asyncio
async def test_app(unused_tcp_port):
    # doing before test
    
    async with TestClient(app, unused_tcp_port) as client:
        res = await client.get('/')
        assert res.text == 'Hello world'

相关应用

  • Nougat Router 一个适用于 Restful API 并且带参数格式化的路由器
  • Nougat Utils 提供了 CLI 支持和调试模式等适用于开发时的工具库

开源协议

本项目使用 MIT 开源,详情请查看 LICENSE 文件

You can’t perform that action at this time.