首页 > 编程语言 >我自己的 Python Web 框架

我自己的 Python Web 框架

时间:2023-02-28 13:33:52浏览次数:60  
标签:Web render 框架 Python 函数 Jar write call 页面

原文地址: https://healeycodes.com/my-own-python-web-framework

在过去的几个月里,我一直在从头开始建立我自己的软件工具--像编程语言文本编辑器CLI工具。在周末,我建立了一个概念验证的网络框架,通过Build Output API部署到Vercel。

一个基于文件系统的规范,允许任何框架为Vercel构建,并利用Vercel的基础设施构建块,如边缘函数、边缘中间件、增量静态再生(ISR)、图像优化等。

Jar是一个玩具Python网络框架,用大约200行代码实现(见 cli.py )。我建立它是为了探索一些围绕框架API的想法,并从作者方面探索框架。请不要真的使用它。它之所以被称为Jar,是因为它几乎没有任何功能,你需要自己去填充它!

它使用文件系统路由并支持。

Jar项目的结构是这样的:

project/
├─ pages/
│  ├─ index.py
├─ public/
│  ├─ favicon.ico

API理念

我对Jar的个人使用情况是在没有前台框架的情况下建立小型动态网站。受到Next.js的API的一点启发,比如 getServerSidePropsgetStaticProps ,Jar的API是由三个函数签名定义的。

  • 数据函数在构建页面和重新生成的页面时被调用。当它在服务器上被调用时,它会收到一个带有方法、路径、头信息和正文的请求对象。

  • render函数接收data函数的返回值,并返回一个 body, info 的元组,其中的信息可以改变响应的状态代码和头文件。

  • 配置函数定义了页面的类型(构建、新鲜或再生)。

这是一个生成页面的例子kitchen sink example:

import time

def render(data):
    return f"<h1>Last regenerated at: {data['time']}</h1>", {}

def data(request=None):
    return {
        "time": time.time()
    }

def config():
    return {
        "regenerate": {
            "every": 5
        }
    }

因为我们是在Python领域,我希望API是灵活的。数据和配置函数是可选的(而且它们不需要接受任何参数)。因此,最小的Jar页面看起来像这样。

render = lambda: (“Hi! I'm a little page.”, {})

构建CLI

在对Jar的CLI进行原型设计时,Build Output API的文档例子足够全面,我没有遇到任何重大问题。通过试验和错误,没过多久我就通过构建和部署真正的项目来测试Jar(从头到尾大约需要6秒钟)。

Jar需要在构建时和在服务器上渲染页面,并使用大量的动态导入和元编程来减少代码行和复杂性。

为了把用户编写的页面当作 Python 模块,在运行时要像这样导入。

module_location = "project/pages/index.py"
spec = importlib.util.spec_from_file_location("", module_location)
page = importlib.util.module_from_spec(spec)
spec.loader.exec_module(page)
# `page` can now be called like `page.render()`

这意味着动态导入的构建页面可以在构建时被调用以生成静态文件。

# `page` is a dynamically imported module e.g. it exists at `pages/index.py`
with open(os.path.join(build_dir, f".vercel/output/static/{request_path}"), "w") as f:
    res = call_render(page)
    f.write(res['body'])
    build_config['overrides'][request_path] = {
        'contentType': res['headers']['Content-Type']
    }

为了创建新鲜和再生的页面,Jar创建了使用 python3.9 运行时的无服务器函数。用于创建构建页面的相同函数(例如 call_data , call_render )被写入一个处理文件,以便它们可以根据需要在服务器上运行。当我说相同的函数时,我的意思是它们是真的从内存中读取的。

def create_handler(path, module_location):
    # the following functions are used at build time to generated build pages
    # and are also used on the server to generated fresh/regenerated pages
    # so we bundle them into a handler file
    with open(path, "w") as f:
        # imports
        f.write("import json\nimport inspect\nimport importlib.util\n")
        f.write('\n')
        # request class
        request_source = inspect.getsource(Request)
        f.write(request_source)
        f.write('\n')
        # call_data function
        call_data_source = inspect.getsource(call_data)
        f.write(call_data_source)
        f.write('\n')
        # call_render function
        call_render_source = inspect.getsource(call_render)
        f.write(call_render_source)
        f.write('\n')
        # app function
        app_source = inspect.getsource(app)
        f.write(app_source.replace("__MODULE_LOCATION", module_location))
        f.write('\n')

构建输出API要求像包这样的外部文件被包含在函数的文件系统中。

一个无服务器功能在文件系统中被表示为一个名称上带有 .func 后缀的目录,包含在 .vercel/output/functions 目录中。
从概念上讲,你可以把这个 .func 目录看作是无服务器功能的文件系统挂载: .func 目录以下的文件被包括在内(递归), .func 目录以上的文件则不包括在内。私人文件可以安全地放在这个目录中,因为它们不会被终端用户直接访问。然而,它们可以被无服务器功能执行的代码所引用。
在 .func 目录下必须包含一个名为 .vc-config.json 的配置文件,其中包含Vercel应该如何构建无服务器功能的信息。

在Jar中,所有的项目文件都被复制到每个函数目录中,以保持简单(更成熟的框架会分割和捆绑以避免每个函数的大小限制)。 .vc-config.json 文件对每个也是一样的。

{"handler": "__handler.app", "runtime": "python3.9", "environment": {}}

函数之间的唯一区别是处理程序在运行时导入的模块(又称页面文件)。

Jar中的一个新的/再生的页面与Serverless/Reperender函数一一对应。当一个请求进入Vercel的边缘网络时,它最终会被路由到处理文件,该文件调用相关页面的 data 和 render 函数,然后回复给客户端。

关于Vercel内部的一些进一步阅读:

文档

无论用户的规模或数量如何,我都喜欢为我的副项目编写文档。它记录了我的想法,帮助我捕捉任何粗糙的边缘,并给我完成项目最后 10% 所需的推动力。也意味着我以后可以随时把东西捡回来!

我为 Jar 写了文档……用 Jar!请在此处查看项目文件。文档使用 marko markdown 包和 Prism.js 进行语法高亮显示(所有 Jar 页面都是纯 Python,没有导入或特殊语法)。

Serverless/Prerender Functions 不知道其函数目录之外的任何内容,因此在使用第三方包时,需要将其安装在项目的根目录下。有一些成熟的方法可以使它正常工作(比如 Python 虚拟环境),但到目前为止我还没有遇到任何问题,只是通过使用 pip 的 --target 参数在本地安装包。

下面是一个示例,在构建和部署 Jar 文档网站的脚本中:

python3 framework/cli.py build examples/docs
# project packages must be installed locally
# so they are bundled correctly when deployed
cd examples/docs && pip3 install -r requirements.txt --target . && cd ../..
cd build && vercel --prebuilt --prod && cd ..

文档涵盖了这个问题,以及有关 API 的更多详细信息,以及每种页面类型的示例。

Tests

有一条有趣的公理说 everything is a compiler, a database, or a combination of both。 Web 框架绝对是编译器——测试编译器(应该具有确定性输出)的一种快速方法是快照测试。

Jar 的测试套件构建两个项目并对文件进行快照测试。对于真正的端到端测试,它可以部署然后卷曲它们以验证生产中的行为没有分歧。

说到确定性输出,我实际上遇到了一个错误,在 CI 中测试有时会失败。该错误是由于 Python 的 json.dumps 在序列化构建配置时如何对键进行排序。

这是我花了三十分钟才找到并修复的错误:

with open(os.path.join(build_dir, '.vercel/output/config.json'), 'w') as f:
-     json.dump(build_config, f)
+     json.dump(build_config, f, sort_keys=True)

做完这个项目,从idea到production一路走来,感觉好像剥了几个计算层。我更喜欢 web 框架 → 编译器 → 生产的流程。

标签:Web,render,框架,Python,函数,Jar,write,call,页面
From: https://www.cnblogs.com/mywindoes/p/17163650.html

相关文章