「译文」如何编写 Python Web 框架(三)

简介: 「译文」如何编写 Python Web 框架(三)

在本系列之前的博客文章中,我们开始编写自己的 Python 框架并实现以下功能:

  • WSGI 兼容
  • 请求处理程序
  • 路由:简单和参数化
  • 检查重复的路径
  • 基于类的处理程序
  • 单元测试

在这部分中,我们将为列表添加一些很棒的功能:

  • 测试客户端
  • 添加路径的替代方式(如类似 Django 的实现)
  • 支持模板

测试客户端

「译文」如何编写 Python Web 框架(二) ,我们编写了几个单元测试。但是,当我们需要向处理程序发送 HTTP 请求时,我们停止了,因为我们没有可以执行此操作的测试客户端。我们先添加一个。

到目前为止,在 Python 中发送 HTTP 请求最流行的方式是 Kenneth ReitzRequests库。但是,为了能够在单元测试中使用它,我们应该始终启动并运行我们的应用程序(即在运行测试之前启动 gunicorn)。原因是 Requests 只附带一个 Transport Adaptter: HTTPAdapter。这违背了单元测试的目的。单元测试应该是自我维持的。对我们来说幸运的是,Sean Brant编写了一个 WSGI Transport Adapter,用于 创建测试客户端。让我们先编写代码再进行讨论。

译者注:

先安装 2 个库:

pip install requests
pip install requests-wsgi-adapter
SHELL

将以下方法添加到 api.py 主类 API 中:

# api.py
...
from requests import Session as RequestsSession
from wsgiadapter import WSGIAdapter as RequestsWSGIAdapter
class API:
    ...
    def test_session(self, base_url="http://testserver"):
        session = RequestsSession()
        session.mount(prefix=base_url, adapter=RequestsWSGIAdapter(self))
        return session
    ...
PYTHON

如此 处所述 ,要使用 Requests WSGI Adapter,我们需要将其 mount 到 Session 对象。这样,使用test_session, 其 URL 以给定前缀开头的任何请求都将使用给定的 RequestsWSGIAdapter。太好了,现在我们可以用test_session 来创建一个测试客户端。创建一个 conftest.py 文件并将api fixture 移动到此文件,使其如下所示:

# conftest.py
import pytest
from api import API
@pytest.fixture
def api():
    return API()
PYTHON

此文件的 pytest 默认情况下会查找 fixture 。现在,让我们在这里创建测试客户端 fixture :

# conftest.py
...
@pytest.fixture
def client(api):
    return api.test_session()
PYTHON

我们的 client 需要 api fixture 并返回我们之前编写的内容test_session。现在我们可以在单元测试中使用这个client fixture 。让我们直接进入test_bumbo.py 文件并编写一个单元测试,测试是否 client 可以发送请求:

# test_bumbo.py
...
def test_bumbo_test_client_can_send_requests(api, client):
    RESPONSE_TEXT = "THIS IS COOL"
    @api.route("/hey")
    def cool(req, resp):
        resp.text = RESPONSE_TEXT
    assert client.get("http://testserver/hey").text == RESPONSE_TEXT
PYTHON

运行单元测试 pytest test_bumbo.py 并观察。我们看到所有的测试都通过了。让我们为最重要的部分添加几个单元测试:

# test_bumbo.py
...
def test_parameterized_route(api, client):
    @api.route("/{name}")
    def hello(req, resp, name):
        resp.text = f"hey {name}"
    assert client.get("http://testserver/matthew").text == "hey matthew"
    assert client.get("http://testserver/ashley").text == "hey ashley"
PYTHON

这个测试我们在 url 中发送的参数是否正常工作。

# test_bumbo.py
...
def test_default_404_response(client):
    response = client.get("http://testserver/doesnotexist")
    assert response.status_code == 404
    assert response.text == "Not found."
PYTHON

这个测试如果请求被发送到不存在的路由,则返回 404(未找到)响应。

剩下的我会留给你。如果您需要任何帮助,请尝试编写更多测试并在评论中告诉我。以下是单元测试的一些想法:

  • 测试基于类的处理程序 GET 请求是否正常运行
  • 测试基于类的处理程序 POST 请求是否正常运行
  • 测试如果使用无效的请求方法,基于类的处理程序返回响应Method Not Allowed.
  • 测试是否正确返回状态码

添加路径的替代方式

现在,这是添加路径的方式:

@api.route("/home")
def handler(req, resp):
    resp.text = "YOLO"
PYTHON

也就是说,路由被添加为装饰器,就像在 Flask 中一样。有些人可能喜欢 Django 注册网址的方式。所以,让我们给他们这样添加路径的选择:

def handler(req, resp):
    resp.text = "YOLO"
def handler2(req, resp):
    resp.text = "YOLO2"
api.add_route("/home", handler)
api.add_route("/about", handler2)
PYTHON

add_route方法应该做两件事。检查路径是否已经注册,如果没有,则注册:

# api.py
class API:
    ...
    def add_route(self, path, handler):
        assert path not in self.routes, "Such route already exists."
        self.routes[path] = handler
PYTHON

很简单。这段代码看起来很熟悉吗?这是因为我们已经在 route 装饰器中编写了这样的代码。我们现在可以遵循 DRY 原则并在 route 装饰器中使用 add_route 方法:

# api.py
class API:
    ...
    def add_route(self, path, handler):
        assert path not in self.routes, "Such route already exists."
        self.routes[path] = handler
    def route(self, pattern):
        def wrapper(handler):
            self.add_route(pattern, handler)
            return handler
    return wrapper
PYTHON

让我们添加一个单元测试来检查它是否正常工作:

# test_bumbo.py
def test_alternative_route(api, client):
    response_text = "Alternative way to add a route"
    def home(req, resp):
        resp.text = response_text
    api.add_route("/alternative", home)
    assert client.get("http://testserver/alternative").text == response_text
PYTHON

运行您的测试,您将看到所有测试都通过。

模板支持

当我实现新的东西时,我喜欢做一些叫做 README 驱动的开发。这是一种技术,您可以在实施之前记下 API 是什么样子。让我们来实现。假设我们要在我们的处理程序中使用此模板:

<html>
    <header>
        <title>{{ title }}</title>
    </header>
    <body>
        The name of the framework is {{ name }}
    </body>
</html>
HTML

{{ title }}{{ name }} 是从处理程序发送的变量,这是处理程序的样子:

api = API(templates_dir="templates")
@api.route("/template")
def handler(req, resp):
    resp.body = api.template("index.html", context={"title": "Awesome Framework", "name": "Alcazar"})
PYTHON

我希望它尽可能简单,所以我只需要一个方法,将模板名和上下文作为参数,并用给定的参数呈现该模板。另外,我们希望模板目录可以像上面一样配置。

通过设计 API,我们现在可以实现它。

对于模板支持,我认为 Jinja2 是最佳选择。它是一个现代的,设计师友好的 Python 模板语言,模仿 Django 的模板。所以,如果你知道 Django, 那么使用 Jinja2 应该感觉一样。

Jinja2使用称为模板 Environment 的中心对象。我们将在应用程序初始化和借助此 Environment 加载模板的基础上配置此环境。以下是如何创建和配置一个:

from jinja2 import Environment, FileSystemLoader
templates_env = Environment(loader=FileSystemLoader(os.path.abspath("templates")))
PYTHON

FileSystemLoader从文件系统加载模板。此加载程序可以在文件系统上的文件夹中查找模板,并且是加载它们的首选方法。它将模板目录的路径作为参数。现在我们可以这样使用templates_env

templates_env.get_template("index.html").render({"title": "Awesome Framework", "name": "Alcazar"})
PYTHON

既然我们了解了 Jinja2 中的所有工作原理,那么我们就将其添加到我们自己的框架中。首先,让我们安装 jinja2:

pip install Jinja2
MIPSASM

然后,在我们的 API 类的 __init__ 方法中创建Environment 对象:

# api.py
from jinja2 import Environment, FileSystemLoader
import os
class API:
    def __init__(self, templates_dir="templates"):
        self.routes = {}
        self.templates_env = Environment(loader=FileSystemLoader(os.path.abspath(templates_dir)))
    ...
PYTHON

我们做了几乎与上面相同的事情,除了我们为 templates_dir 提供了一个默认值,templates以便用户不必写它。现在我们有了实现我们之前设计的 template 方法的所有方法:

# api.py
class API:
    def template(self, template_name, context=None):
        if context is None:
            context = {}
        return self.templates_env.get_template(template_name).render(**context)
PYTHON

我认为这里没有必要解释任何事情。你唯一想知道的是为什么我给了 context 一个默认值 None,检查它是否是None,然后将值设置为空字典{}。你可能会说我可以在声明中给它默认值{}。但是dict 它是一个可变对象,在 Python 中将可变对象设置为默认值是一种不好的做法。在这里 阅读更多相关信息。

随着一切准备就绪,我们可以创建模板和处理程序。首先,创建 templates 文件夹:

mkdir templates
SHELL

通过执行 touch templates/index.html 创建文件 index.html 并将以下内容放入:

<html>
    <header>
        <title>{{ title }}</title>
    </header>
    <body>
        <h1>The name of the framework is {{ name }}</h1>
    </body>
</html>
HTML

现在我们可以在我们的 app.py 创建处理程序:

# app.py
@app.route("/template")
def template_handler(req, resp):
    resp.body = app.template("index.html", context={"name": "Alcazar", "title": "Best Framework"})
PYTHON

就是这样(好吧,差不多)。启动 gunicorn 然后访问 http://localhost:8000/template。你会看到一个大大的Internal Server Error。那是因为resp.body 期望 bytes, 而我们的 template 方法返回一个 unicode 字符串。因此,我们需要对其进行编码:

# app.py
@api.route("/template")
def template_handler(req, resp):
    resp.body = app.template("index.html", context={"name": "Alcazar", "title": "Best Framework"}).encode()
PYTHON

重新启动 gunicorn,你将看到我们的模板的所有荣耀。在后续的文章中,我们将不再需要 encode 并使我们的 API 更漂亮。

结论

我们在这篇文章中实现了三个新功能:

  • 测试客户端
  • 添加路径的替代方式(如 Django 的实现方式)
  • 支持模板

请务必在评论中告诉我们应该在本系列中实现的其他功能。对于下一部分,我们肯定会添加对静态文件的支持,但我不确定我们应该添加哪些其他功能。

稍微提醒一下,这个系列是基于我为学习目的而编写的 Alcazar 框架。如果你喜欢这个系列, 请在这儿 查看博客中的内容,一定要通过 star 该 repo 来表达你的喜爱。

Fight on!

相关文章
|
1天前
|
数据采集 Web App开发 存储
打造高效的Web Scraper:Python与Selenium的完美结合
本文介绍如何使用Python结合Selenium,通过代理IP、设置Cookie和User-Agent抓取BOSS直聘的招聘信息,包括公司名称、岗位、要求和薪资。这些数据可用于行业趋势、人才需求、企业动态及区域经济分析,为求职者、企业和分析师提供宝贵信息。文中详细说明了环境准备、代理配置、登录操作及数据抓取步骤,并提醒注意反爬虫机制和验证码处理等问题。
打造高效的Web Scraper:Python与Selenium的完美结合
|
22天前
|
人工智能 开发者 Python
Chainlit:一个开源的异步Python框架,快速构建生产级对话式 AI 应用
Chainlit 是一个开源的异步 Python 框架,帮助开发者在几分钟内构建可扩展的对话式 AI 或代理应用,支持多种工具和服务集成。
137 9
|
1月前
|
关系型数据库 API 数据库
Python流行orm框架对比
Python中有多个流行的ORM框架,如SQLAlchemy、Django ORM、Peewee、Tortoise ORM、Pony ORM、SQLModel和GINO。每个框架各有特点,适用于不同的项目需求。SQLAlchemy功能强大且灵活,适合复杂项目;Django ORM与Django框架无缝集成,易用性强;Peewee轻量级且简单,适合小型项目;Tortoise ORM专为异步框架设计;Pony ORM查询语法直观;SQLModel结合Pydantic,适合FastAPI;GINO则适合异步环境开发。初学者推荐使用Django ORM或Peewee,因其易学易用。
|
1月前
|
人工智能 分布式计算 大数据
MaxFrame 产品评测:大数据与AI融合的Python分布式计算框架
MaxFrame是阿里云MaxCompute推出的自研Python分布式计算框架,支持大规模数据处理与AI应用。它提供类似Pandas的API,简化开发流程,并兼容多种机器学习库,加速模型训练前的数据准备。MaxFrame融合大数据和AI,提升效率、促进协作、增强创新能力。尽管初次配置稍显复杂,但其强大的功能集、性能优化及开放性使其成为现代企业与研究机构的理想选择。未来有望进一步简化使用门槛并加强社区建设。
80 7
|
1月前
|
JSON 安全 中间件
Python Web 框架 FastAPI
FastAPI 是一个现代的 Python Web 框架,专为快速构建 API 和在线应用而设计。它凭借速度、简单性和开发人员友好的特性迅速走红。FastAPI 支持自动文档生成、类型提示、数据验证、异步操作和依赖注入等功能,极大提升了开发效率并减少了错误。安装简单,使用 pip 安装 FastAPI 和 uvicorn 即可开始开发。其优点包括高性能、自动数据验证和身份验证支持,但也存在学习曲线和社区资源相对较少的缺点。
84 15
|
1月前
|
安全 前端开发 数据库
Python 语言结合 Flask 框架来实现一个基础的代购商品管理、用户下单等功能的简易系统
这是一个使用 Python 和 Flask 框架实现的简易代购系统示例,涵盖商品管理、用户注册登录、订单创建及查看等功能。通过 SQLAlchemy 进行数据库操作,支持添加商品、展示详情、库存管理等。用户可注册登录并下单,系统会检查库存并记录订单。此代码仅为参考,实际应用需进一步完善,如增强安全性、集成支付接口、优化界面等。
|
2月前
|
分布式计算 大数据 数据处理
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
116 2
|
2月前
|
JSON 数据可视化 测试技术
python+requests接口自动化框架的实现
通过以上步骤,我们构建了一个基本的Python+Requests接口自动化测试框架。这个框架具有良好的扩展性,可以根据实际需求进行功能扩展和优化。它不仅能提高测试效率,还能保证接口的稳定性和可靠性,为软件质量提供有力保障。
96 7
|
2月前
|
敏捷开发 测试技术 持续交付
自动化测试之美:从零开始搭建你的Python测试框架
在软件开发的马拉松赛道上,自动化测试是那个能让你保持节奏、避免跌宕起伏的神奇小助手。本文将带你走进自动化测试的世界,用Python这把钥匙,解锁高效、可靠的测试框架之门。你将学会如何步步为营,构建属于自己的测试庇护所,让代码质量成为晨跑时清新的空气,而不是雾霾中的忧虑。让我们一起摆脱手动测试的繁琐枷锁,拥抱自动化带来的自由吧!
|
3月前
|
开发者 Docker Python
从零开始:使用Docker容器化你的Python Web应用
从零开始:使用Docker容器化你的Python Web应用
110 1

热门文章

最新文章

  • 1
    打造高效的Web Scraper:Python与Selenium的完美结合
    13
  • 2
    Burp Suite Professional 2025.2 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
    26
  • 3
    AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试
    20
  • 4
    【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
    54
  • 5
    部署使用 CHAT-NEXT-WEB 基于 Deepseek
    342
  • 6
    【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
    26
  • 7
    java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
    40
  • 8
    零基础构建开源项目OpenIM桌面应用和pc web- Electron篇
    28
  • 9
    【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
    22
  • 10
    FastAPI与Selenium:打造高效的Web数据抓取服务 —— 采集Pixabay中的图片及相关信息
    55
  • 推荐镜像

    更多