Python中如何封装阿里云SDK以改善其使用体验?

槽多无口, 里面的接口还有 v20140815 , 所有的 class 看起来都一样, 一样的长, 一样的难以理解

举个例子 CreateOnlineDataBaseTaskRequest

# 阿里云的代码
from aliyunsdkcore.request import RpcRequest
class CreateOnlineDatabaseTaskRequest(RpcRequest):
def __init__(self):
    RpcRequest.__init__(self, 'Rds', '2014-08-15', 'CreateOnlineDatabaseTask','rds')

def get_ResourceOwnerId(self):
    return self.get_query_params().get('ResourceOwnerId')

def set_ResourceOwnerId(self,ResourceOwnerId):
    self.add_query_param('ResourceOwnerId',ResourceOwnerId)

def get_MigrateTaskId(self):
    return self.get_query_params().get('MigrateTaskId')

def set_MigrateTaskId(self,MigrateTaskId):
    self.add_query_param('MigrateTaskId',MigrateTaskId)

def get_DBName(self):
    return self.get_query_params().get('DBName')

def set_DBName(self,DBName):
    self.add_query_param('DBName',DBName)

def get_ResourceOwnerAccount(self):
    return self.get_query_params().get('ResourceOwnerAccount')

def set_ResourceOwnerAccount(self,ResourceOwnerAccount):
    self.add_query_param('ResourceOwnerAccount',ResourceOwnerAccount)

def get_ClientToken(self):
    return self.get_query_params().get('ClientToken')

def set_ClientToken(self,ClientToken):
    self.add_query_param('ClientToken',ClientToken)

def get_OwnerAccount(self):
    return self.get_query_params().get('OwnerAccount')

def set_OwnerAccount(self,OwnerAccount):
    self.add_query_param('OwnerAccount',OwnerAccount)

def get_DBInstanceId(self):
    return self.get_query_params().get('DBInstanceId')

def set_DBInstanceId(self,DBInstanceId):
    self.add_query_param('DBInstanceId',DBInstanceId)

def get_CheckDBMode(self):
    return self.get_query_params().get('CheckDBMode')

def set_CheckDBMode(self,CheckDBMode):
    self.add_query_param('CheckDBMode',CheckDBMode)

def get_OwnerId(self):
    return self.get_query_params().get('OwnerId')

def set_OwnerId(self,OwnerId):
    self.add_query_param('OwnerId',OwnerId)

# 我想要的代码
class DatabaseTask:
    def __init__(self, **kwargs):
        self.migrate_task = migrate_task
        self.db_name = db_name
        self.resource_owner = resource_owner
        self.token = token
        self.check_db_mode = check_db_mode
    
    def run():
        # make some request

if __name__ == '__main__':
    new_task = DatabaseTask(migrate_task=1, db_name='some_db',
                            resource_owner='some_user', token='some_token',
                            check_db_mode='some_mode')
    result = new_task.run()
    result.fetch()

为什么不写成 一个 DatabaseTask 对象, 对象有几个属性值 Instance_id 等 ,然后再调用 DatabaseTask.create()

这些 set 和 get 的方法, 不就是实现了 python 的属性值吗?

我这里想问一下大家, 如果我这么做了, 会收到律师函吗?


Python中如何封装阿里云SDK以改善其使用体验?

35 回复

这种代码一看就是 先有 Java 的 SDk, 实现 Python SDK 时直接照搬了 Java 的实现。 这种行为虽然挺恶心的, 但毕竟是最节省开发资源的做法,也只能忍了 。

Google 的 Python SDK 一样大量这种代码。


抛开代码风格的问题, 阿里云 Python SDK 早期的代码质量极差, 连培训班水平都不如。 目前的质量已经比以前好太多了。
https://github.com/aliyun/aliyun-openapi-python-sdk/issues/43


封装阿里云SDK主要是为了简化调用、统一错误处理和配置管理。下面是一个封装OSS(对象存储)的示例,展示了如何通过一个类来隐藏SDK的初始化细节,提供更简洁的上传方法。

import oss2
from typing import Optional, BinaryIO

class AliOSSClient:
    def __init__(self, access_key_id: str, access_key_secret: str, endpoint: str, bucket_name: str):
        """初始化OSS客户端"""
        auth = oss2.Auth(access_key_id, access_key_secret)
        self.bucket = oss2.Bucket(auth, endpoint, bucket_name)
    
    def upload_file(self, object_name: str, file_path: str) -> bool:
        """上传本地文件到OSS"""
        try:
            self.bucket.put_object_from_file(object_name, file_path)
            print(f"文件 {file_path} 已上传至 {object_name}")
            return True
        except oss2.exceptions.OssError as e:
            print(f"上传失败: {e}")
            return False
    
    def upload_data(self, object_name: str, data: BinaryIO) -> bool:
        """上传二进制数据到OSS"""
        try:
            self.bucket.put_object(object_name, data)
            print(f"数据已上传至 {object_name}")
            return True
        except oss2.exceptions.OssError as e:
            print(f"上传失败: {e}")
            return False

# 使用示例
if __name__ == "__main__":
    client = AliOSSClient(
        access_key_id="your_key_id",
        access_key_secret="your_key_secret",
        endpoint="oss-cn-hangzhou.aliyuncs.com",
        bucket_name="your_bucket"
    )
    
    # 上传文件
    client.upload_file("test/example.jpg", "/local/path/example.jpg")
    
    # 上传二进制数据
    with open("/local/path/example.txt", "rb") as f:
        client.upload_data("test/example.txt", f)

这个封装做了几件事:1)把认证和Bucket初始化放在构造函数里;2)统一用try-except处理OSS异常;3)提供两种常见上传方式。你可以根据实际需求继续添加下载、删除、列表等方法。

建议根据项目需要继续扩展这个封装类。

支持 这种带有 java “风味”的 python 代码是 python 中的毒瘤

我之前看他们的代码以为是我功力不够的原因…原来是他们写的查,汗!

get set 有啥问题

估计是直接根据 Java SDK 代码用编译器编译成 Python 代码的(代码生成)吧,其他语言类似。这样只需要维护一份 Java 代码即可

在保证代码正确性的前提下,代码不够 pythonic 还是可以接受的

妈耶
这代码要人命啊

支持,每次用 Python 的 SDK 用的想死

java 味太浓

这是自动化生成的接口代码。。。

之前用过他们的 python sdk,sdk 代码应该是根据 api 生成的。
可以用 aliyunsdkcore.request 中的 CommonRequest,自己封装了个通用的方法,将就用一下 orz

这明显是自动生成的代码。。和机器人较真干啥。

这个 sdk 是干啥用的

用过阿里云的短信 SDK,调用的时候感觉巨恶心

这种要么是基于 Java 生成,要么直接根据 OAI 类似 swagger 的规范直接一把生成,不分 java 还是啥

亲们,大家好,我是阿里云 SDK 的研发 GG,看到吐槽就赶紧诚惶诚恐地过来啦^_^。首先感谢大家对阿里云 Python SDK 的关注,这里跟大家十分十分(* 1024 )诚恳地道歉,Python SDK 没有按照 Python 的编码规范设计,我们没做好,给大家造成了不好的体验,对不起!

为了解决好这个这个问题呢,我们阿里云 SDK 研发团队正在抓紧时间编写阿里云 Python SDK 的第二版,目前是刚起步,项目代码在这里: https://github.com/aliyun/alibabacloud-python-sdk-v2 请大家过目。亲们可以在: https://github.com/aliyun/alibabacloud-python-sdk-v2/issues 这里吐槽,我们会抓紧时间改进哦!

亲们的批评就是我们改进的动力,为此我们会不断努力,感谢对阿里云的关心!

想起了金山云的 SDK,fork 自 aws 的,然后大家表示读不懂代码,宁愿调用我的 http 接口都不愿意自己去接入。

在国内的项目组里呢一般都只有边缘人和实习生才会干搞 SDK 这类事情, 质量差是很自然的事情

v20140815 有什么问题? 公有云产品的 API 必须向下兼容,即便某天发布了 v2020xxxx,先前的 v20140815 也是要永久保留的。
SDK 里 client 与 request 各司其职,request 负责封装 API 的参数; client 负责从 request 取参数、做签名、拼 url 发请求、解析返回值。且各个产品的各个 API 都 follow 此 pattern,学一次就会用所有产品的 API 了。
你自己定义的 DatabaseTask 相比之下并没看出有什么优点,仅仅是满足了你自己的偏好而已。

我已经很知足了,比部分云厂商的好很多

这个回复应该是妹子客服回复的吧,这文笔不像是程序 gg…

代码烂不是槽点,依赖一个老掉牙的加密库才是





你们高估阿里云了, 这代码就是让实习生抄出来的

相比之下 微软的 azure python sdk 如同神赐一般

好像 java

谢谢你的不留情面的批评, 可能是我孤陋寡闻了, 但是国外的云厂商, 我在 aws 的 sdk https://github.com/boto/botocore/tree/develop/botocore 里没有发现版本的印记, 在 azure 的 sdk 里, 版本的印记还存在, 但是一些最近有修改的模块已经没有了版本的文件夹 https://github.com/Azure/azure-sdk-for-python .

这说明带时间的版本已经不是最佳实践, azure 也在尝试改变这一点, 完全可以通过 SDK 的主版本进行控制

带时间的版本我认为也确实不友好, 比如 20140817 版本和 20180704 版本兼容吗? 用户是不清楚的 , 如果是类似 0.0.1 和 3.0.0 , 那么用户自己就有感觉, 这个代码可能是不兼容了,再参阅一下文档, 确实是不兼容的, 那么, 要么改代码, 要么安装低版本的 sdk.

我给出的版本确实是满足了自己的偏好, 但我认为是确实比之前的容易理解的, 把 get set 改为对对象的属性值操作

既然可以
<br>new_task = DatabaseTask()<br>new_task.owner = 'me'<br>print(new_task.owner)<br>

那么为什么要
<br>new_task = DatabaseTask()<br>new_task.set_owner('me')<br>print(new_task.get_owner('me'))<br>

而且属性值也天生支持动态获取, 动态设置

<br>new_task.setattr('owner','me')<br>new_task.getattr('owner')<br>
如果是函数的话, 没有办法做到动态的.

我认为这在 python 中是更友好的一种调用方法, 你觉得呢?

新的已经不依赖了, 感恩

那还挺不错。不过已经被我们用一个 5 行的 python 函数+urllib 给代替了。

https://github.com/Azure/azure-sdk-for-python/blob/master/azure-mgmt-dns/azure/mgmt/dns/models.py
确实是有, 但是你看一下这里, 在 models 层做了一个快捷方式, 默认情况下, 是调 v2018_03_01_preview.models 的, 对于开发者来说, 如果版本的需求, 是可以不用在代码里体现 v2018_03_01_preview 这些冗长的字符的.

这些快捷方式, 阿里做了吗? 没有.



关于 API 的向下兼容:并非指的是 v2020xxxx 要兼容 v20140815 的功能,而是说即便发布了 v2020xxxx 后,v20140815 依然可用并且行为不变。 对于调用方来说,升级 api 版本或者 sdk 版本都是非常 critical 的事,在升级过程中,校验 sdk 或 api 的版本兼容性是必不可少的步骤。当然如你所说,sdk 版本和 api 版本强绑定是个糟糕的设计。

属性和 setter 之间的取舍,你看一下阿里 sdk 的源码,它是在 setter 里把 params 放到了父类的一个 dictionary 里了。另外基于它代码中有 add_query_param()的字样,我推测除了 query params 应该还有 body params、header_params、这些是基于 api metadata 生成 sdk 的 request 就固定下来的,如果不用 setter 的方式就需要额外的 metadata 来描述一个 param 的位置到底是在 query、body 还是 header 中。

最后关于动态设置 request 的属性,我自己是阿里云 ecs/rds/slb 等产品 java sdk 的重度用户,其实不太明白为什么有“动态设置属性”这种需求。你有什么场景是必须要动态设置属性的嘛?

我最后提到动态属性的意思是, 属性值是有好处的, 代码风格上会看起来更简单易读.

Python 和 Java 类似, 也可以有 setter 和 getter 函数

不一样的是, python 还有 property 装饰器, 加入这个装饰器, 就可以正常使用 student.age del(student.age)这种做法了
<br>class Student(object):<br> def __init__(self):<br> self._age = None<br><br> <br> def age(self):<br> return self._age<br><br> .setter<br> def age(self, age):<br> if isinstance(age, int):<br> self._age = age<br> return<br> if isinstance(age, str) and age.isdigit():<br> age = int(age)<br> self._age = age<br> else:<br> raise ValueError("age is illegal")<br><br> .deleter<br> def age(self):<br> del self._age<br>
我还顺着翻到了它的 base 类 https://github.com/aliyun/aliyun-openapi-python-sdk/blob/master/aliyun-python-sdk-core/aliyunsdkcore/request.py 到了 base 依然是有着大量的普通的 set_XXX / set_XXX 方法.

而我因为他们代码都是 set_XXX 的, 我也得写这样冗长的代码, 为什么不改成上面的那种呢?

我的意思是, python 里有很多的语法糖可以做这些事情, 但是阿里云的 sdk 都没有用到.

看着真像自动生成的…

回到顶部