Python编程:Request中models.py

在 requests 库的源代码中,models.py 文件定义了核心的请求和响应模型类,主要包括 RequestPreparedRequest 和 Response 三个类。这些类共同构成了 requests 库处理 HTTP 请求和响应的基础架构。


文件结构概览

models.py 主要包含以下内容:

  1. Mixin 类

    • RequestHooksMixin:管理请求钩子(hooks)

    • RequestEncodingMixin:处理 URL 编码和文件上传

  2. 核心类

    • Request:用户创建的请求对象

    • PreparedRequest:准备好发送的请求对象

    • Response:服务器返回的响应对象

类关系图

 

核心协作流程

(1) Request → PreparedRequest
  • Request (用户层)

    • 用户直接操作的类,提供友好的参数接口(如 methodurljson 等)

    • 继承 RequestHooksMixin 管理钩子(hooks)

    • 核心方法prepare()
      调用后会生成一个 PreparedRequest 对象,完成请求的最终组装。

  • PreparedRequest (传输层)

    • 继承 RequestEncodingMixin(处理 URL/参数编码、文件上传)和 RequestHooksMixin

    • 负责将 Request 的抽象参数转换为实际要发送的字节数据:

      • 编码 URL 和查询参数(prepare_url()

      • 处理请求体(JSON/表单/文件,prepare_body()

      • 添加认证头(prepare_auth()

      • 生成最终的 HTTP 头和 Cookie(prepare_headers()prepare_cookies()

  • 关键点
    Request 是“配方”,PreparedRequest 是“烹饪好的菜品”,后者才是实际发送到服务器的对象。

(2) PreparedRequest → Response
  • 发送请求后,服务器返回的响应会被封装为 Response 对象。

  • Response 中会保留对原始 PreparedRequest 的引用(通过 response.request 属性),形成闭环。

 RequestEncodingMixin类

class RequestEncodingMixin:
    @property
    def path_url(self):
        """Build the path URL to use."""

        url = []

        p = urlsplit(self.url)

        path = p.path
        if not path:
            path = "/"

        url.append(path)

        query = p.query
        if query:
            url.append("?")
            url.append(query)

        return "".join(url)

    @staticmethod
    def _encode_params(data):
        """Encode parameters in a piece of data.

        Will successfully encode parameters when passed as a dict or a list of
        2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
        if parameters are supplied as a dict.
        """

        if isinstance(data, (str, bytes)):
            return data
        elif hasattr(data, "read"):
            return data
        elif hasattr(data, "__iter__"):
            result = []
            for k, vs in to_key_val_list(data):
                if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
                    vs = [vs]
                for v in vs:
                    if v is not None:
                        result.append(
                            (
                                k.encode("utf-8") if isinstance(k, str) else k,
                                v.encode("utf-8") if isinstance(v, str) else v,
                            )
                        )
            return urlencode(result, doseq=True)
        else:
            return data

    @staticmethod
    def _encode_files(files, data):
        """Build the body for a multipart/form-data request.

        Will successfully encode files when passed as a dict or a list of
        tuples. Order is retained if data is a list of tuples but arbitrary
        if parameters are supplied as a dict.
        The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
        or 4-tuples (filename, fileobj, contentype, custom_headers).
        """
        if not files:
            raise ValueError("Files must be provided.")
        elif isinstance(data, basestring):
            raise ValueError("Data must not be a string.")

        new_fields = []
        fields = to_key_val_list(data or {})
        files = to_key_val_list(files or {})

        for field, val in fields:
            if isinstance(val, basestring) or not hasattr(val, "__iter__"):
                val = [val]
            for v in val:
                if v is not None:
                    # Don't call str() on bytestrings: in Py3 it all goes wrong.
                    if not isinstance(v, bytes):
                        v = str(v)

                    new_fields.append(
                        (
                            field.decode("utf-8")
                            if isinstance(field, bytes)
                            else field,
                            v.encode("utf-8") if isinstance(v, str) else v,
                        )
                    )

        for k, v in files:
            # support for explicit filename
            ft = None
            fh = None
            if isinstance(v, (tuple, list)):
                if len(v) == 2:
                    fn, fp = v
                elif len(v) == 3:
                    fn, fp, ft = v
                else:
                    fn, fp, ft, fh = v
            else:
                fn = guess_filename(v) or k
                fp = v

            if isinstance(fp, (str, bytes, bytearray)):
                fdata = fp
            elif hasattr(fp, "read"):
                fdata = fp.read()
            elif fp is None:
                continue
            else:
                fdata = fp

            rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
            rf.make_multipart(content_type=ft)
            new_fields.append(rf)

        body, content_type = encode_multipart_formdata(new_fields)

        return body, content_type

RequestEncodingMixin 是一个混合类,提供了三种主要的请求编码功能,用于处理 HTTP 请求的不同部分:路径 URL参数文件上传。下面详细解释每个方法的功能:

1. path_url 属性

@property
def path_url(self):
    """构建要使用的路径 URL"""
功能:
  • 从请求的完整 URL 中提取路径部分和查询字符串

  • 确保路径至少包含一个斜杠 "/"

  • 保留查询字符串部分(如果有)

处理逻辑:

  1. 使用 urlsplit 解析 URL

  2. 获取路径部分,如果为空则设置为 "/"

  3. 如果有查询字符串,则附加 "?" 和查询字符串

  4. 将所有部分拼接成完整的路径 URL

示例:

  • 输入 http://example.com/api?q=test → 输出 /api?q=test

  • 输入 http://example.com → 输出 /

2. _encode_params 静态方法

@staticmethod
def _encode_params(data):
    """编码数据中的参数"""
功能:
  • 将参数编码为 URL 查询字符串格式

  • 处理多种输入类型:字符串、字节、文件对象、字典或元组列表

  • 支持多值参数(同一个键对应多个值)

  • 自动处理 Unicode 编码

处理逻辑:

  1. 如果数据是字符串或字节,直接返回

  2. 如果数据有 read 方法(文件对象),直接返回

  3. 如果是可迭代对象(字典或元组列表):

    • 将数据转换为键值对列表

    • 处理多值情况(将单个值转换为列表)

    • 对键和值进行 UTF-8 编码(如果是字符串)

    • 使用 urlencode 生成查询字符串

示例:

  • 输入 {'q': '测试', 'page': 1} → 输出 q=%E6%B5%8B%E8%AF%95&page=1

  • 输入 [('q', 'a'), ('q', 'b')] → 输出 q=a&q=b

3. _encode_files 静态方法

@staticmethod
def _encode_files(files, data):
    """为 multipart/form-data 请求构建请求体"""
功能:
  • 构建用于文件上传的 multipart/form-data 格式请求体

  • 处理普通表单字段和文件字段

  • 支持多种文件格式:文件名+文件对象、带内容类型的、带自定义头部的

  • 自动猜测文件名(如果未提供)

处理逻辑:

  1. 验证输入(必须有文件,数据不能是字符串)

  2. 处理普通表单字段:

    • 转换为键值对列表

    • 处理多值情况

    • 处理编码问题

  3. 处理文件字段:

    • 解析不同的文件格式(2-4元组)

    • 读取文件内容

    • 创建 RequestField 对象

    • 设置为 multipart 格式

  4. 使用 encode_multipart_formdata 生成最终的请求体和内容类型

示例:

files = {'file': ('test.txt', open('test.txt', 'rb'), 'text/plain')}
data = {'name': 'value'}
# 生成 multipart/form-data 格式的请求体和对应的 Content-Type

总结

RequestEncodingMixin 提供了完整的 HTTP 请求编码功能:

  • 处理 URL 路径和查询字符串

  • 处理 URL 编码的参数

  • 处理 multipart 文件上传

这些方法通常被 HTTP 客户端库(如 requests)内部使用,用于构建不同类型的 HTTP 请求。

RequestHooksMixin 类

class RequestHooksMixin:
    def register_hook(self, event, hook):
        """Properly register a hook."""

        if event not in self.hooks:
            raise ValueError(f'Unsupported event specified, with event name "{event}"')

        if isinstance(hook, Callable):
            self.hooks[event].append(hook)
        elif hasattr(hook, "__iter__"):
            self.hooks[event].extend(h for h in hook if isinstance(h, Callable))

    def deregister_hook(self, event, hook):
        """Deregister a previously registered hook.
        Returns True if the hook existed, False if not.
        """

        try:
            self.hooks[event].remove(hook)
            return True
        except ValueError:
            return False

RequestHooksMixin 是一个混合类,提供了钩子(hook)的注册和注销功能,用于在请求过程中插入自定义处理逻辑。下面详细解释每个方法的功能:

1. register_hook 方法

def register_hook(self, event, hook):
    """正确注册一个钩子"""
功能:
  • 为特定事件注册一个或多个钩子函数

  • 支持注册单个可调用对象或可迭代的多个可调用对象

  • 验证事件名称是否有效

  • 验证钩子是否可调用

参数:

  • event: 要注册钩子的事件名称

  • hook: 可以是单个可调用对象,或包含多个可调用对象的可迭代对象

处理逻辑:

  1. 检查事件名称是否存在于 self.hooks 中,不存在则抛出 ValueError

  2. 如果钩子是单个可调用对象(Callable),添加到该事件的钩子列表

  3. 如果钩子是可迭代对象,遍历并添加其中所有可调用对

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值