Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wechatpy 2.0 的一些想法 #120

Closed
hunter007 opened this issue Dec 29, 2015 · 29 comments
Closed

wechatpy 2.0 的一些想法 #120

hunter007 opened this issue Dec 29, 2015 · 29 comments

Comments

@hunter007
Copy link
Contributor

wehctapy 已经在我们的项目中用了几个月,确实方便了我们很多。

但也有些用的别扭的地方。慢慢的也产生了一些想法。希望和你交流一下。

出现此想法的原因

  • 在项目中使用时,感觉现有些接口比较难用。
  • 接口返回数据是纯 json,而非对象
  • 只支持官方操作
  • 不够对象化
  • 发布时非接口类功能缺失
  • 没有对接 python 常用框架

改进方向:易用性功能性

接口返回数据是纯 json,而非对象

这在项目中还要做各种处理,这种处理放在业务中意义不大,浪费精力,适合放到本库中。提供一下接口会更好:
做法:将微信中的概念变成对象,将接口变成对象的方法,方法返回对象实例。对象实例提供各种数据格式的转化能力。

follower_manager =   wechat_client.get_manager('fellower')
# 获取所有关注者
followers = follower_manager.get()
# 获取单个关注者
follower =   wechat_client.fellowers.get(name='nickname')
# 修改
follower.nickname = 'another_name'
follower.sex ='
follower.save()

shakearound_manager =   wechat_client.get_manager('shakearound')
page_manager = shakearound_manager.get_manager('page')
all_pages = page_manager.get()
page = page_manager.get(page_id)
new_page = page_manager.add(title, description, url, icon_url, mark='')
new_page.json()
new_page.xml()

只支持官方操作

可以在官方操作的基础上封装常用操作,这样更方便使用

不够对象化

wechat_client = WechatClient(app_id, app_secret)

实例化之后,就可以检查wechat_client可调用的对象(底层接口),没有权限时,抛出异常;
所有操作以面向对象的方式使用,屏蔽 http 层。

发布时非接口类功能缺失

发布项目时,经常需要额外开发。比如验证 token,开放平台的基本测试等,可以在库中实现。节省调用者开发时间。

没有对接 python 常用框架

多数情况下微信对象需要本地保存,如果能对象 django ORM、SqlAlchemy ORM 等数据库模型,会大大增加我们项目的友好度。比如 Django 中

from wechatpy.decorators import bind_model
from wechatpy.models import Fellower
from django.db import models

@bind_model(Fellower)
class WechatFellower(models.Model):
     name = models.CharField(...)

这样就把 wechatpy 中的模型和 Django 的模型关联了。

wechat_fellower = WechatFellower.objects.get(name='xxx')
wechat_fellower.name = 'another_name'
wechat_fellower.save()

在完成 django 模型功能(保存到数据库)的同时,也完成了和微信接口的交互。

下面是 wechatpy 2.0 架构:
2015-12-29 13 09 16

  1. 我们可以通过 python 内置的 http 库或者 requests 等第三方库封装对微信 http 接口的调用
  2. 在此基础上,将微信概念对象化,将接口变成这些对象的操作,并可以提供非官方操作。
  3. 提供一种机制(微信对象适配层),可以将这些对象和一些常用 python 库的数据模型关联。

绿色部分将是新 wechatpy 对外提供的功能。当微信接口变化时,我们修改黄色部分即可。

这个想法还不够细致。如果你对这个改进方向认可,我们可以就此多想一想​细节。

@messense
Copy link
Member

cc @cloverstd

@cloverstd
Copy link
Collaborator

这个改进很好
我目前在使用的过程中,会结合微信文档来看,所以对于我来说

接口返回数据是纯 json,而非对象

由于 wechatpy 文档不全,或者说,我主要用在企业号,我不得不去看源代码和结合微信文档来调用 API

对于 Python 常用框架,我觉得可以作为类似于 Flask 的扩展一样,这个 @messense 好像也说过,作为一个扩展来添加使用,而不是集成在 wechatpy 里。

@messense
Copy link
Member

接口返回数据是纯 json,而非对象

Python 中 json 对象就是个 dict,用起来非常方便,这里我不希望过度封装,假如需要封装的话也不会直接在 WeChatClient 上做,可能需要提供另外一个 Client

只支持官方操作
可以在官方操作的基础上封装常用操作,这样更方便使用

这个可以有,但不是非常重要。

不够对象化
实例化之后,就可以检查wechat_client可调用的对象(底层接口),没有权限时抛出异常;所有操作以面向对象的方式使用,屏蔽 http 层。

抽象 http 层是需要做的,更面向对象需要更详细的说明。

发布时非接口类功能缺失

验证 token 你指的是啥?

没有对接 python 常用框架

这个每个框架都不一样,可以考虑弄一个 wechatpy.contrib 来做这个事情。

我目前的想法是先把 wechatpy 分拆成多个 namespace package,see https://github.com/jxtech/wechatpy/blob/master/wechatpy/__init__.py#L3

@messense
Copy link
Member

@hunter007
Copy link
Contributor Author

没有对接 python 常用框架
如果是公众号开发,上线前需要项目代码验证 token;
如果是开放平台,上线时微信会对项目做一系列测试,通过了才能上线。
以及其他的一些非接口但在开发中属于常用功能。

@messense
Copy link
Member

没有对接 python 常用框架
如果是公众号开发,上线前需要项目代码验证 token;
如果是开放平台,上线时微信会对项目做一系列测试,通过了才能上线。
以及其他的一些非接口但在开发中属于常用功能。

可以有

@hunter007
Copy link
Contributor Author

这个每个框架都不一样,可以考虑弄一个 wechatpy.contrib 来做这个事情。

这个需要我们实现一个机制(或者叫协议)才方便对接其他 ORM。要不要在我们的项目中实现都可以,看精力。

@messense
Copy link
Member

这个每个框架都不一样,可以考虑弄一个 wechatpy.contrib 来做这个事情。
这个需要我们实现一个机制(或者叫协议)才方便对接其他 ORM。要不要在我们的项目中实现都可以,看精力。

用 namespace package 的话,放到 wechatpy.contrib 之类的看起来比较顺眼,反正代码可以分散在多个 repo 中不太会增加 core repo 的复杂度。

@hunter007
Copy link
Contributor Author

Python 中 json 对象就是个 dict,用起来非常方便,这里我不希望过度封装,假如需要封装的话也不会直接在 WeChatClient 上做,可能需要提供另外一个 Client

关键是在调用者那边是如何用的。这些内容在调用者那边基本都是对象。如果不是返回对象,那调用者不得不封装这个json。另外,在我看来,json 是数据格式,不是实体,而是实体的一种表现。json 的作用在于存储,但缺少操作的能力。或许我们可以搞一些 serializer 去输出不同的格式,或者在对象上提供输出这些格式的方法。

@messense
Copy link
Member

关键是在调用者那边是如何用的。这些内容在调用者那边基本都是对象。如果不是返回对象,那调用者不得不封装这个json。另外,在我看来,json 是数据格式,不是实体,而是实体的一种表现。json 的作用在于存储,但缺少操作的能力。或许我们可以搞一些 serializer 去输出不同的格式,或者在对象上提供输出这些格式的方法。

如果只是用来做简单的公众号相关的功能,很多东西都不需要存储,调用之后直接从 dict 中取数据最简便、最少 overhead。Python is not Java,不希望变成和那些 Java 的微信 SDK 一样封装一切。我希望保持核心简单,然后可以做一些扩展。

@hunter007
Copy link
Contributor Author

用 namespace package 的话,放到 wechatpy.contrib 之类的看起来比较顺眼,反正代码可以分散在多个 repo 中不太会增加 core repo 的复杂度。

关于这点我还是一个基础观点,对不想使用的项目来说,不要增加负担就行。
比如wechatpy.contrib.django对接了 django,wechatpy.contrib.flask对接了 flask。当我要用在 flask 中时,不需要安装 django。我支持的方式是出现一个wechatpy-django。它依赖 wechatpy 和 django,可以用在 django 的项目中以方便地在 django 中使用 wechatpy。

在具体一点的话,我们在 wechatpy 中实现一个 backend,在wechatpy-django中引入 backend来扩展。这个可以我们来写,也可以由别人来写。

@messense
Copy link
Member

关于这点我还是一个基础观点,对不想使用的项目来说,不要增加负担就行。
比如wechatpy.contrib.django对接了 django,wechatpy.contrib.flask对接了 flask。当我要用在 flask 中时,不需要安装 django。我支持的方式是出现一个wechatpy-django。它依赖 wechatpy 和 django,可以用在 django 的项目中以方便地在 django 中使用 wechatpy。

在具体一点的话,我们在 wechatpy 中实现一个 backend,在wechatpy-django中引入 backend来扩展。这个可以我们来写,也可以由别人来写。

这个不是问题,用 namespace package 的话,wechatpywechatpy.contrib 甚至 wechatpy.contrib.xxx 可以分布在多个 package 中,需要用的就 pip install 就好,核心库 wechatpy 可以瘦身。

参考 zope 相关的库的发布方式。

@hunter007
Copy link
Contributor Author

如果只是用来做简单的公众号相关的功能,很多东西都不需要存储,调用之后直接从 dict 中取数据最简便、最少 overhead。Python is not Java,不希望变成和那些 Java 的微信 SDK 一样封装一切。我希望保持核心简单,然后可以做一些扩展。

我并没有用其他语言的思想来考虑。如果说有哪些参照的话,更多是 django 的做法。关于封装的程度,我觉得把接口封装成对象是基本的面向对象的封装,并没有在这个对象上再进行封装。你可能觉得serializer就封装过渡了,其实django 就是这么做的。

@hunter007
Copy link
Contributor Author

这个不是问题,用 namespace package 的话,wechatpy 和 wechatpy.contrib 甚至 wechatpy.contrib.xxx 可以分布在多个 package 中,需要用的就 pip install 就好,核心库 wechatpy 可以瘦身。

参考 zope 相关的库的发布方式。

这个我赞成。和我说的wechatpy-django是一个意思,名称不一样而已。

@messense
Copy link
Member

Namespace package. FYI:

http://zhengkun.info/2015/06/18/python_namespace.html

@messense
Copy link
Member

我并没有用其他语言的思想来考虑。如果说有哪些参照的话,更多是 django 的做法。关于封装的程度,我觉得把接口封装成对象是基本的面向对象的封装,并没有在这个对象上再进行封装。你可能觉得serializer就封装过渡了,其实django 就是这么做的。

这样做呢,wechatpy.models 里面去定义各种 data model,WeChatClient 返回结果保持 json 不变,data model 可以有 from_json/from_dict 之类的 factory method 去构建,或者用 serializer 来构建。你需要 data model,那么你就自己去实例化,而不是默认给你的就是 data model,在此之上可以提供更方便的默认返回 data model 的 WeChatClient

@hunter007
Copy link
Contributor Author

Namespace package. FYI:
http://zhengkun.info/2015/06/18/python_namespace.html

这个非常赞成。

@hunter007
Copy link
Contributor Author

这样做呢,wechatpy.models 里面去定义各种 data model,WeChatClient 返回结果保持 json 不变,data model 可以有 from_json/from_dict 之类的 factory method 去构建,或者用 serializer 来构建。你需要 data model,那么你就自己去实例化,而不是默认给你的就是 data model,在此之上可以提供更方便的默认返回 data model 的 WeChatClient

其实你只要想一想调用者拿到数据之后会自然怎么做就知道哪个方案更好了。这个看法离我的观点很近了,就差一步:

from wechatpy.models import WechatGroup
group_result = client.group.get(name='xxx')
wechat_group = WechatGroup(group_result)

如果把

group_result = client.group.get(name='xxx')

放到client.group.get()里面,并在失败时抛出异常,代码会变成这样。

try:
    wechat_group = client.group.get(name='xxx')
except SomeException as e:
    print(e)
else:
    wechat_group.get_fellowers()

@messense
Copy link
Member

所以你想实现的是,部分 data model 上依然可以调用 API 咯?就像你举的这个 group 的例子。

@hunter007
Copy link
Contributor Author

是的。将微信的概念封装成对象,接口变成对象的方法。完全屏蔽接口。

@messense
Copy link
Member

是的。将微信的概念封装成对象,接口变成对象的方法。完全屏蔽接口。

没有问题,只是不希望改变默认的 WeChatClient 的默认行为,尽可能不要 break 掉别人的代码。

@hunter007
Copy link
Contributor Author

这些对象可以通过某种方法联通其他 python 库。比如装饰器,或者注册机制。

@messense
Copy link
Member

这些对象可以通过某种方法联通其他 python 库。比如装饰器,或者注册机制。

嗯,需要做些 prototype 出来看看

@hunter007
Copy link
Contributor Author

没有问题,只是不希望改变默认的 WeChatClient 的默认行为,尽可能不要 break 掉别人的代码。

是的。确实有这个问题。我们可以建立2.0分支。1.0分支只有 bugfix 和优化。2.0的对外接口提供1.0的兼容。慢慢过渡,想 python 本身一样。

嗯,需要做些 prototype 出来看看

这个还只是想法,有哪些好的实现,大家一起考虑一下。
django 的 admin 应用中有这样的代码:

@admin.register(WechatGroup)
class WechatGroupModelAdmin(admin.ModelAdmin):
    list_display = ('name', 'groupid', 'wechat', 'count')

WechatGroup是 django 的 model,WechatGroupModelAdmin是管理视图的封装。

那我们是不是可以写出这样的代码:

from wechatpy.models import WechatGroup
from wechatpy.decortors import bind_model
from django.db import models

@bind_model(WechatGroup)
class Group(models.Model):
    pass

或者其他方案。

这只是一个示例,大家在一起考虑。

@openpython
Copy link

不建议过度封装,wechatpy应该保持自己的独立性,完善微信功能异常处理以及在此基础上的高级功能,不应该和对象以及ORM扯上关系,很多场景还是需要手写SQL的。为了方便其他WEB框架使用,可以考虑开发对应插件,简化调用,如flask插件等。

@hunter007
Copy link
Contributor Author

是不是可以考虑全面优化一下wechatpy了?

建议将wechatpy/wechatpy和jxtech/wechatpy同步一次,然后在wechatpy/wechatpy上建立2.0分支。wechatpy/wechatpy的master分支还是和jxtech/wechatpy保持同步,但jxtech/wechatpy只修复bug。

@messense
Copy link
Member

@hunter007

https://github.com/wechatpy/wechatpy/tree/v2

你有些想法的话可以先做起来。

@messense messense added this to the wechatpy v2.0 milestone Apr 1, 2017
@ftp2010
Copy link

ftp2010 commented Apr 14, 2019

应该也把小程序包括在内

@huimingz
Copy link
Contributor

huimingz commented May 1, 2020

试着用描述器的方式实现自动获取值,最初只需要传入一个字典结果值到模型中,值获取是一个懒处理的过程,处理完后的值会缓存。另外使用了typing解决类型注解问题

以下是参考:

data = {
    "errcode": 0,
    "errmsg": "ok",
    "checkindata": [{
        "userid": "james",
        "groupname": "打卡一组",
        "checkin_type": "上班打卡",
        "exception_type": "地点异常",
        "checkin_time": 1492617610,
        "location_title": "依澜府",
        "location_detail": "四川省成都市武侯区益州大道中段784号附近",
        "wifiname": "办公一区",
        "notes": "路上堵车,迟到了5分钟",
        "wifimac": "3c:46:d8:0c:7a:70",
        "mediaids": ["WWCISP_G8PYgRaOVHjXWUWFqchpBqqqUpGj0OyR9z6WTwhnMZGCPHxyviVstiv_2fTG8YOJq8L8zJT2T2OvTebANV-2MQ"]
    }, {
        "userid": "paul",
        "groupname": "打卡二组",
        "checkin_type": "外出打卡",
        "exception_type": "时间异常",
        "checkin_time": 1492617620,
        "location_title": "重庆出口加工区",
        "location_detail": "重庆市渝北区金渝大道101号金渝大道",
        "wifiname": "办公室二区",
        "notes": "",
        "wifimac": "3c:46:d8:0c:7a:71",
        "mediaids": ["WWCISP_G8PYgRaOVHjXWUWFqchpBqqqUpGj0OyR9z6WTwhnMZGCPHxyviVstiv_2fTG8YOJq8L8zJT2T2OvTebANV-2MQ"],
        "lat": 30547645,
        "lng": 104063236,
        "deviceid": "E5FA89F6-3926-4972-BE4F-4A7ACF4701E2"
    }]
}

from typing import List


class DataDescriptor(object):

    def __set_name__(self, owner, name):
        if not hasattr(self, "_name"):
            self._name = name

    def __init__(self, name=None, type_=None):
        if name:
            self._name = name
        self._type = type_

    def __get__(self, instance, type_=None):
        if not instance:
            return
        dataset = instance._data[self._name]
        if self._type:
            if isinstance(dataset, list):
                res = [self._type(data) for data in dataset]
            else:
                res =  self._type(dataset)
        else:
            res = dataset
        instance.__dict__[self._name] = res
        return res


class DataModel(object):
    class CheckinDataModel(object):

        def __init__(self, data: dict) -> None:
            self._data = data

        userid: str = DataDescriptor()
        groupname: str = DataDescriptor()
        checkin_type: str = DataDescriptor()

    def __init__(self, data: dict) -> None:
        self._data = data

    checkindata: List[CheckinDataModel] = DataDescriptor(type_=CheckinDataModel)

    @property
    def checkin_data(self) -> List[CheckinDataModel]:
        pass


d = DataModel(data)
print(d.checkindata)
print(d.checkindata[0].userid)
print(d.checkindata[0].userid)

@wechatpy wechatpy locked and limited conversation to collaborators Aug 18, 2021
1.x maintenance automation moved this from Todo to Done Aug 18, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
Development

No branches or pull requests

6 participants