Package: nougat
# Nougat基于 Python 3.6 的异步框架。Nougat 使用中间件的形式来处理逻辑。![PyPI](https://img.shields.io/pypi/pyversions/nougat.svg) ![PyPI](https://img.shields.io/pypi/status/nougat.svg) ![PyPI](https://img.shields.io/pypi/v/nougat.svg) ![PyPI](https://img.shields.io/pypi/l/nougat.svg) [![Build Status](https://travis-ci.org/Kilerd/nougat.svg?branch=master)](https://travis-ci.org/Kilerd/nougat)## 安装Nougat 目前仅支持 Python3.6，所以你可以通过 PYPI 来安装：```bashpip3 install nougat  # or pip install nougat```或者你可以通过 pipenv 来安装(推荐)：```bashpipenv install nougat```### 开发版本如果你想使用 Nougat 的新特性，可以选择使用开发版本。**注意这同时也意味着框架会存在着更多的BUG** ：```bashpip3 install git@github.com:Kilerd/nougat.git@develop```### Uvloop 支持Nougat 依赖于 asyncio，所以如果你打算把 Nougat 运行在 Linux 上，可以选择使用更好的异步库来运行首先安装 `uvloop`:```bashpip3 install uvloop```然后在项目中添加以下代码以使用 uvloop:```pythonimport asyncioimport uvloopasyncio.set_event_loop_policy(uvloop.EventLoopPolicy())```## 快速上手```pythonfrom nougat import Nougatapp = 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` 方法可以同时传入多个中间件，注册顺序跟传入顺序一致```pythonasync def one_middleware():passasync 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 = 1normal = 2app.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` 里面，按需定义```Pythonasync def middleware(app, request, response, next):# doing before requestawait next()# doing after after```- `app` 为 `Nougat` 的实例- `request` 为 `Request` 实例，当前请求上下文的请求对象- `response` 为 `Response`实例，当前请求上下文的响应对象- `next` 为中间件链的下一个中间件，经由包装后的无参数方法，可直接`await next() ` 调用**注意，为了可以得到更好的代码提示，建议在编写中间件参数时，使用 Type Hints。 `async def middleware(app: Nougat, request: Request, response: Response, next)`**```pythonasync def middleware(app, request, next):if request.headers.get('Authentication'):print('get user token')await next()app.use(middleware)```上述中间件使用了三个参数，并没有使用到`response`或者，我们可以编写一个类作为中间件```pythonclass Middleware:async def __call__(next):await next()app.use(Middleware())```#### 中间件执行顺序```Request+                 ^|                 |+------|-----------------|-------+|      |                 |       ||      |   middleware 1  |       ||      |                 |       |+------|-----------------|-------+|                 |+------|-----------------|-------+|      |                 |       ||      |   middleware 2  |       ||      |                 |       |+------|-----------------|-------+|                 ||                 |+-----------------+````await next()` 把一个中间件分成两个部分（当找不到该语句时，整个中间件默认被理解为上半部分），Nougat 在处理中间件时，会正向处理中间件链的上半部分，逆向处理下半部分```pythonv = []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]`#### 记录处理时间的例子```Pythonasync def logging_time(response, next):start_time = time()await next()process_time = time() - start_timeresponse.set_header("Time": "{}s".format(process_time))```### 信号信号用于 Nougat 服务器启动前后处理的事情，例如建立数据库连接，数据的预处理等等目前，Nougat 只支持两种信号，启动服务器前信号 `before_start` 和 关闭服务器后信号`after_start````python@app.signal('before_start')async def handle(app):pass```使用 `app.signal` 装饰器可以完成该工作，参数为信号值。 装饰器函数可以为异步函数，也可以为普通函数。参数为 `app`，其类型为 Nougat 类的实例信号的具体应用请自行想象## 高级使用说明### 请求类 RequestRequest 类用于储存 HTTP 请求的相关信息#### request.methodHTTP 访问方法，为大写字符串 `GET`, `POST` 等#### request.versionHTTP 协议版本#### request.urlHTTP 请求的URL，为 `yarl.URL` 实例，以下是一些操作实例```Pythonurl = yarl.URL("https://example.com/foo/bar?hello=world")print(url.host)  # example.comprint(url.path)  # /foo/barprint(url.query)  # <MultiDictProxy('hello': 'world')>print(url.query.get('hello'))  # world```#### request.content_typeHTTP 的 `Content-Type`#### request.query`request.url.query` 的快捷访问方式#### request.formHTTP 非 GET 方法中 FORM 数据储存。#### request.ipHTTP 访问者的 IP#### request.headersHTTP 请求头部信息#### request.cookiesHTTP 请求的 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` 来实现这个功能```pythonasync 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 提供了一个非常方便的库，用于执行单元测试。当然了，我们依赖于`pytest` 和 `pytest-asyncio` 来提供基础配件```pythonimport pytestfrom app import appfrom nougat import TestClient@pytest.mark.asyncioasync def test_app(unused_tcp_port):# doing before testasync with TestClient(app, unused_tcp_port) as client:res = await client.get('/')assert res.text == 'Hello world'```## 相关应用- [Nougat Router](https://github.com/Kilerd/nougat-router) 一个适用于 Restful API 并且带参数格式化的路由器- [Nougat Utils](https://github.com/Kilerd/nougat-utils) 提供了 CLI 支持和调试模式等适用于开发时的工具库## 开源协议本项目使用 MIT 开源，详情请查看 [LICENSE](https://github.com/NougatWeb/nougat/blob/master/LICENSE) 文件