我希望_User在登录后检测一个指向DEUser表的字段若空则新建一条DEUser数据,并在DEUser数据里反响保存一个冗余的指向_User的字段,代码如下:

@engine.on_login
def on_login(user):
    '''登录检测时'''
    print 'on_login被调用, user = ', user
    checkAndLateInit_User(user) #检查_User表所有相关的关系和数据,若无则新建


def checkAndLateInit_User(user):
    '''检查_User表所有相关的关系和数据,若无则新建;本方法调用了user.save(),所以请在after系列接口中调用,不要在before系列接口中调用'''
    print 'checkAndLateInit_User被调用, user = ', user
    
    #若无deUser账号关联则创建
    deUser = user.get('deUser')
    if not deUser:
        DEUser = leancloud.Object.extend('DEUser')
        aDeUser = DEUser()
        aDeUser.set('user', user)
        user.set('deUser', aDeUser)
        user.save()

运行后注册成功,User里新建一条数据成功,但该条数据的deUser字段为空;DEUser里新建的一条数据成功,指向User的pointer数据也正确。控制台的消息如下:

> INFO web1 17:05:37 before_after_User被调用, user =  <leancloud.object_._User object at 0x7fd792f93910>
> INFO web1 17:05:37 checkAndLateInit_User被调用, user =  <leancloud.object_._User object at 0x7fd792f93910>
> INFO web1 17:05:37 Traceback (most recent call last):
> INFO web1 17:05:37   File "/usr/local/lib/python2.7/dist-packages/leancloud/engine/leanengine.py", line 115, in dispatch_request
> INFO web1 17:05:37     result = dispatch_cloud_hook(**values)
> INFO web1 17:05:37   File "/usr/local/lib/python2.7/dist-packages/leancloud/engine/leanengine.py", line 244, in dispatch_cloud_hook
> INFO web1 17:05:37     func(obj)
> INFO web1 17:05:37   File "/home/leanengine/app/cloud.py", line 35, in before_after_User
> INFO web1 17:05:37     checkAndLateInit_User(user) #检查_User表所有相关的关系和数据,若无则新建
> INFO web1 17:05:37   File "/home/leanengine/app/cloud.py", line 59, in checkAndLateInit_User
> INFO web1 17:05:37     user.save()
> INFO web1 17:05:37   File "/usr/local/lib/python2.7/dist-packages/leancloud/object_.py", line 215, in save
> INFO web1 17:05:37     self._deep_save(unsaved_children, unsaved_files, exclude=self._attributes)
> INFO web1 17:05:37   File "/usr/local/lib/python2.7/dist-packages/leancloud/object_.py", line 265, in _deep_save
> INFO web1 17:05:37     raise errors[0]
> INFO web1 17:05:37 LeanCloudError: <unprintable LeanCloudError object>

我还尝试过:
1 将user.save()改为aDeUser.save();
2 同时存在两条save(),aDeUser.save()在前;
3 同时存在两条save,并在aDeUser.save()后调用了aDeUser.disable_after_hook()。
执行结果全部与上面相同。

能不能在 user.save 调用上加一层 try ... except ...,看一下抛出来的异常对象到底是什么内容?

比如改成这样:

try:
    user.save()
except leancloud.LeanCloudError as e:
    import inspect
    print 'code:', inspect.getmembers(e.code)
    print 'error:', inspect.getmembers(e.error)

    INFO web1 11:48:07 after_save_User被调用, user =  <leancloud.object_._User object at 0x7f44d3fcc510>
    INFO web1 11:48:07 checkAndLateInit_User被调用, user =  <leancloud.object_._User object at 0x7f44d3fcc510>
    INFO web1 11:48:07 user.deUser 为 not,创建新DEUser并关联
    INFO web1 11:48:07 code: [('__class__', <type 'NoneType'>), ('__delattr__', <method-wrapper '__delattr__' of NoneType object at 0x91a870>), ('__doc__', None), ('__format__', <built-in method __format__ of NoneType object at 0x91a870>), ('__getattribute__', <method-wrapper '__getattribute__' of NoneType object at 0x91a870>), ('__hash__', <method-wrapper '__hash__' of NoneType object at 0x91a870>), ('__init__', <method-wrapper '__init__' of NoneType object at 0x91a870>), ('__new__', <built-in method __new__ of type object at 0x911420>), ('__reduce__', <built-in method __reduce__ of NoneType object at 0x91a870>), ('__reduce_ex__', <built-in method __reduce_ex__ of NoneType object at 0x91a870>), ('__repr__', <method-wrapper '__repr__' of NoneType object at 0x91a870>), ('__setattr__', <method-wrapper '__setattr__' of NoneType object at 0x91a870>), ('__sizeof__', <built-in method __sizeof__ of NoneType object at 0x91a870>), ('__str__', <method-wrapper '__str__' of NoneType object at 0x91a870>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x911920>)]
    INFO web1 11:48:07 error: [('__class__', <type 'dict'>), ('__cmp__', <method-wrapper '__cmp__' of dict object at 0x7f44d3b64050>), ('__contains__', <built-in method __contains__ of dict object at 0x7f44d3b64050>), ('__delattr__', <method-wrapper '__delattr__' of dict object at 0x7f44d3b64050>), ('__delitem__', <method-wrapper '__delitem__' of dict object at 0x7f44d3b64050>), ('__doc__', "dict() -> new empty dictionary\ndict(mapping) -> new dictionary initialized from a mapping object's\n    (key, value) pairs\ndict(iterable) -> new dictionary initialized as if via:\n    d = {}\n    for k, v in iterable:\n        d[k] = v\ndict(**kwargs) -> new dictionary initialized with the name=value pairs\n    in the keyword argument list.  For example:  dict(one=1, two=2)"), ('__eq__', <method-wrapper '__eq__' of dict object at 0x7f44d3b64050>), ('__format__', <built-in method __format__ of dict object at 0x7f44d3b64050>), ('__ge__', <method-wrapper '__ge__' of dict object at 0x7f44d3b64050>), ('__getattribute__', <method-wrapper '__getattribute__' of dict object at 0x7f44d3b64050>), ('__getitem__', <built-in method __getitem__ of dict object at 0x7f44d3b64050>), ('__gt__', <method-wrapper '__gt__' of dict object at 0x7f44d3b64050>), ('__hash__', None), ('__init__', <method-wrapper '__init__' of dict object at 0x7f44d3b64050>), ('__iter__', <method-wrapper '__iter__' of dict object at 0x7f44d3b64050>), ('__le__', <method-wrapper '__le__' of dict object at 0x7f44d3b64050>), ('__len__', <method-wrapper '__len__' of dict object at 0x7f44d3b64050>), ('__lt__', <method-wrapper '__lt__' of dict object at 0x7f44d3b64050>), ('__ne__', <method-wrapper '__ne__' of dict object at 0x7f44d3b64050>), ('__new__', <built-in method __new__ of type object at 0x918de0>), ('__reduce__', <built-in method __reduce__ of dict object at 0x7f44d3b64050>), ('__reduce_ex__', <built-in method __reduce_ex__ of dict object at 0x7f44d3b64050>), ('__repr__', <method-wrapper '__repr__' of dict object at 0x7f44d3b64050>), ('__setattr__', <method-wrapper '__setattr__' of dict object at 0x7f44d3b64050>), ('__setitem__', <method-wrapper '__setitem__' of dict object at 0x7f44d3b64050>), ('__sizeof__', <built-in method __sizeof__ of dict object at 0x7f44d3b64050>), ('__str__', <method-wrapper '__str__' of dict object at 0x7f44d3b64050>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x918de0>), ('clear', <built-in method clear of dict object at 0x7f44d3b64050>), ('copy', <built-in method copy of dict object at 0x7f44d3b64050>), ('fromkeys', <built-in method fromkeys of type object at 0x918de0>), ('get', <built-in method get of dict object at 0x7f44d3b64050>), ('has_key', <built-in method has_key of dict object at 0x7f44d3b64050>), ('items', <built-in method items of dict object at 0x7f44d3b64050>), ('iteritems', <built-in method iteritems of dict object at 0x7f44d3b64050>), ('iterkeys', <built-in method iterkeys of dict object at 0x7f44d3b64050>), ('itervalues', <built-in method itervalues of dict object at 0x7f44d3b64050>), ('keys', <built-in method keys of dict object at 0x7f44d3b64050>), ('pop', <built-in method pop of dict object at 0x7f44d3b64050>), ('popitem', <built-in method popitem of dict object at 0x7f44d3b64050>), ('setdefault', <built-in method setdefault of dict object at 0x7f44d3b64050>), ('update', <built-in method update of dict object at 0x7f44d3b64050>), ('values', <built-in method values of dict object at 0x7f44d3b64050>), ('viewitems', <built-in method viewitems of dict object at 0x7f44d3b64050>), ('viewkeys', <built-in method viewkeys of dict object at 0x7f44d3b64050>), ('viewvalues', <built-in method viewvalues of dict object at 0x7f44d3b64050>)]
    INFO web1 11:48:07 after_save_DEUser被调用, deUser =  <leancloud.object_.DEUser object at 0x7f44d3fccfd0>
    INFO web1 11:48:07 checkAndLateInit_DEUser被调用, deUser =  <leancloud.object_.DEUser object at 0x7f44d3fccfd0>
    INFO web1 11:48:07 deUser.deck为 not,创建新Deck并关联

最后3条记录是我写的别的hook的函数执行的print,也可以看出来deUser的更新是成功的,但是_User.deUser段仍然为空

_User.deUser 为空的原因是 user.save() 的时候抛了异常,保存没有成功。稍等我看一下相关的日志。

不好意思,理论上讲上面捕获的 LeanCloudError 异常应该有这次保存失败的具体原因,保存在 e.error 里,但是可能因为 SDK 的 bug,看上面的日志, e.error 成了一个 dict。

不知道能不能修改一下上面贴的代码,把 e.error 打印出来吗?

代码大概是这样的:

try:
    user.save()
except leancloud.LeanCloudError as e:
    print e.error

好的,稍等,我试一下。ps请问我用unity捕获的leancloud异常应该如何print啊?也是需要import类似的头文件吗?https://forum.leancloud.cn/t/exception-message/10441

INFO web1 14:30:05 after_save_User被调用, user =  <leancloud.object_._User object at 0x7f38b40a7510>
INFO web1 14:30:05 checkAndLateInit_User被调用, user =  <leancloud.object_._User object at 0x7f38b40a7510>
INFO web1 14:30:05 user.deUser 为 not,创建新DEUser并关联
INFO web1 14:30:05 {u'code': 1, u'error': u'Not Found'}
INFO web1 14:30:05 after_save_DEUser被调用, deUser =  <leancloud.object_.DEUser object at 0x7f38b40a7fd0>
INFO web1 14:30:05 checkAndLateInit_DEUser被调用, deUser =  <leancloud.object_.DEUser object at 0x7f38b40a7fd0>
INFO web1 14:30:05 deUser.deck为 not,创建新Deck并关联

这里 aDeUser.save() 应该放到 user.save() 之前,现在的报错信息提示的就是这个错误。按您帖子最早的描述,这样也会有问题,不过还是需要改一下,解决了目前的问题,再看一下真正错误的地方。

我将代码改成以下这样,结果跟之前一样:

def checkAndLateInit_User(user):
    '''检查_User表所有相关的关系和数据,若无则新建;本方法调用了user.save(),所以请在after系列接口中调用,不要在before系列接口中调用'''
    print 'checkAndLateInit_User被调用, user = ', user

    #若无deUser账号关联则创建
    deUser = user.get('deUser')
    if not deUser:
        print('user.deUser 为 not,创建新DEUser并关联')
        DEUser = leancloud.Object.extend('DEUser')
        aDeUser = DEUser()
        aDeUser.set('user', user) #暂时不存这个冗余了,会出错
        try:
            aDeUser.save()
        except leancloud.LeanCloudError as e:
            print e.error

        user.set('deUser', aDeUser)
        try:
            user.save()
        except leancloud.LeanCloudError as e:
            #import inspect
            #print 'code:', inspect.getmembers(e.code)
            #print 'error:', inspect.getmembers(e.error)
            print e.error

log信息如下:

INFO web1 17:06:05 after_save_User被调用, user =  <leancloud.object_._User object at 0x7f03e16de190>
INFO web1 17:06:05 checkAndLateInit_User被调用, user =  <leancloud.object_._User object at 0x7f03e16de190>
INFO web1 17:06:05 user.deUser 为 not,创建新DEUser并关联
INFO web1 17:06:05 {u'code': 1, u'error': u'Not Found'}
INFO web1 17:06:05 {u'code': 1, u'error': u'Not Found'}
INFO web1 17:06:05 after_save_DEUser被调用, deUser =  <leancloud.object_.DEUser object at 0x7f03e16de250>
INFO web1 17:06:05 checkAndLateInit_DEUser被调用, deUser =  <leancloud.object_.DEUser object at 0x7f03e16de250>
INFO web1 17:06:05 deUser.deck为 not,创建新Deck并关联

另外因为首先多处文档中都提到如果有这种带pointer的情况,只调用带有pointer的那个数据.save()就可以,其次没调用aDeUser.save()时这条aDeUser也会成功存储,所以我感觉这里好像并不需要调用aDeUser.save()

我在本地已经复现问题了,请稍等。

实在不好意思,发现是 Python SDK 本身的 bug,Python SDK 的 Object.deepsave 在保存一个已经存在的对象时请求到了错误的 URL,导致出现这个问题的。您上面的代码刚好触发了这种情况。现在已经在这个 PR2 里进行了修复。我们会在 code review 通过之后尽快发布新版本的 SDK 的。

如果您急需这个功能,可以直接在 requirements.txt 里将之前的 leancloud-sdk 修改为 # git+https://github.com/aisk/leancloud-python-sdk.git@fix-deep-saver#egg=leancloud-sdk 来直接指定开发版本的代码来绕过这个问题。另外请记得在新版 SDK 发布之后改回原来的 SDK。

原来是这样,非常感谢!
这个是存的冗余的pointer,只是为了方便,并不着急用,我等新版SDK发布吧。

因为在官网正式版现在的SDK也有一些问题,比如array保存时元素都是null,客户端调用AVUser.CurrentUser == null检测时在某些情况下出错,我使用的是这个beta版本https://github.com/leancloud/Unity-SDK/releases/tag/v2.0.0-beta.1。请问你说的新版SDK发布,是在这个beta版的基础之上吧?请问我应该关注哪里得知最新版SDK发布的消息?

问题已收到,AVUser.CurrentUser == null 判断会报错?我这边试一下吧。

谢谢。这里是问题描述。但我用了beta版SDK就没问题了。

感谢您提供的信息。我会增加这部分的测试,目测应该是一个强类型转化的问题。

确认 Python 的问题已修复。