wechatpy 项目的前世今生

wechatpy 是什么？

wechatpy 项目是一个 Python 语言实现的微信公众号开发 SDK，代码托管在 GitHub 上：https://github.com/wechatpy/wechatpy

wechatpy 前世

wechatpy 是我 2014 年大学还没毕业在杭州一家使用 Python 开发的创业公司实习的时候创建的项目。当时微信公众号 Python SDK 应该也已经出现了好些个了，一开始公司要做微信公众号开发项目的时候，我选择的是当时网友 @whtsky 开发的 WeRobot 框架（之前也和 @whtsky 合作开发过 Catsup 项目）。

当时使用的 Django 框架，在使用 WeRobot 接入完被动响应功能之后，觉得我想要的微信公众号开发 SDK 更多的是一个库（library）而不是一个框架(framework)，库和框架的主要区别我认为在于：比如实现同一个功能，库是用户调用库提供的 API，而框架则是框架调用用户提供的 API。就实现微信公众号被动响应功能来说，使用库的方式就已经足够简单了，框架当然也很简单但对维护框架的开发者来说需要为各种 Web 框架再做适配。

而之后接入主动调用接口的时候，因为微信开放的接口数量繁多，WeRobot 把所有的接口都组织在 werobot.Client 类下，我个人不是很喜欢这种做法，并且当时也看了 nameko 的代码，我很喜欢它的那种命名空间的 API 组织方式，便想尝试按自己的想法写一个微信公众号 Python SDK，由此 wechatpy 项目诞生。

wechatpy 接口设计

wechatpy 项目初期，复用了 WeRobot 的一些代码，非常感谢 @whtsky 和 WeRobot 项目贡献者（也曾在 WeRobot 项目发起过使用 wechatpy 来做解析消息等工作，WeRoBot 专注 robot 层面的东西 的提议，只是被否了）。除此之外，wechatpy 有着几个受到 Django ORM 和 nameko 影响的接口设计。

一、被动响应消息

微信被动响应消息的格式是 XML，解析本质上就是把 XML 里面的字段转换成 Python 的 dict，这个使用 xmltodict 库可以很方便地实现。但是直接使用 dict 代码中很难知道有哪些字段可以使用，要是有 schema 就好了，当时还没了解过 marshmallow，dataclasses 和 pydantic 都还没有面世，基于对 Django ORM model 实现代码的学习，设计了一套类似的机制。

定义了 BaseField 提供 from_xml 和 to_xml 抽象 XML 字段序列化和反序列化，再继承 BaseField 实现具体的字段类型，比如 StringField 代码如下：

class StringField(BaseField):

def __to_text(self, value):

return to_text(value)

converter = __to_text

def to_xml(self, value):

value = self.converter(value)

return f"<{self.name}> {value} </{self.name}>"

@classmethod

def from_xml(cls, value):

return value

f-strings 的用法是最近 @LKI 贡献的，:-)

有了这些字段类型的定义之后，就可以进行消息 model 的定义了，wechatpy 使用 Python 的 metaclass 特性实现了字段类型到数据的序列化和反序列化操作，具体可以参考 MessageMetaClass 代码。

被动响应消息的回复实现也很类似，在 MessageMetaClass 的基础上，BaseReply 增加了 render() 方法，调用字段的 to_xml 方法将回复 model 序列化为 XML，调用示例：

from wechatpy.replies import TextReply

reply = TextReply(content='text reply', message=msg)

# 或者

reply = TextReply(message=msg)

reply.content = 'text reply'

# 转换成 XML

xml = reply.render()

二、主动调用接口

主动调用接口主要是实现了接口的分组，比如主动消息接口都在 WeChatClient.message 实例下，调用方法如下：

from wechatpy import WeChatClient

client = WeChatClient('appid', 'secret')

# 发送图片消息

res = client.message.send_image('openid', 'media_id')

# 查询自定义菜单

menu = client.menu.get()

这种接口的设计也是基于 Python 的 metaclass 特性实现的，主要代码在 BaseWeChatClient 类的 __new__ 方法上。

import inspect

from wechatpy.client.api.base import BaseWeChatAPI

def _is_api_endpoint(obj):

return isinstance(obj, BaseWeChatAPI)

class BaseWeChatClient:

def __new__(cls, *args, **kwargs):

self = super().__new__(cls)

api_endpoints = inspect.getmembers(self, _is_api_endpoint)

for name, api in api_endpoints:

api_cls = type(api)

api = api_cls(self)

setattr(self, name, api)

return self

使用 inspect.getmembers 获取到类成员中的继承自 BaseWeChatAPI 的实例

通过 type 内置函数获取继承自 BaseWeChatAPI 的实例成员的类型后重新初始化并传入当前 WeChatClient 实例作为构造函数的参数，用 setattr 内置函数替换原来的成员

之后需要实现具体的微信主动调用接口，只需要集成 BaseWeChatAPI 并实例为 WeChatClient 的一个成员变量就行了，以菜单接口为例：

from wechatpy.client.base import BaseWeChatClient

from wechatpy.client.api.base import BaseWeChatAPI

class WeChatMenu(BaseWeChatAPI):

def get(self):

# 实现代码省略

class WeChatClient(BaseWeChatClient):

API_BASE_URL = "https://api.weixin.qq.com/cgi-bin/"

menu = WeChatMenu()

就可以愉快地使用 client.menu.get() 方法调用了。

wechatpy 今生

wechatpy 项目从创建到今天，已经获得了 2400 多 GitHub 星标，有 600 多 forks，活跃的代码贡献者 3~4 个（感谢！），QQ 群约 500 成员（经常清理不活跃的成员方便新人进群），依然是个比较小的项目。wechatpy 当前支持的微信开放平台功能有：

公众号平台被动响应和主动调用 API

企业微信 API

微信支付 API

第三方平台代公众号调用接口 API

小程序和小程序云开发 API

也有一些基于 wechatpy 做的周边项目，如：

Odoo 的微信模块

Odoo WeChat API

flask-wechatpy

等。

wechatpy 未来

目前 wechatpy 项目已经在 2.0 版本的迭代中，最大的改变是放弃了 Python 2 的支持，并使用 Poetry 进行依赖管理。

非常欢迎参与 wechatpy 项目开发的讨论和代码贡献，谢谢。

爱发电上赞助