Package: rainbond-python
Rainbond Python一个完整的 Python 云原生应用开发解决方案，基于以下内容：Flask：轻量级 Python Web 应用程序框架Rainbond：开源的企业级云原生平台，撑企业应用开发、架构、交付和运维的全流程安装$pipinstallrainbond-python
或者
$pip3installrainbond-python快速开始通过rainbond-python -c <组件名称>命令可以创建一个 Python 云原生组件：rainbond-python-cdemo_component该命令会在当前目录下创建一个组件项目，并打印如下提示：Successfullycreated`demo_component`component.Runcommand:
$cddemo_component
$pip3install-rrequirements.txt
$python3app.py按照上面的提示，可以把一个最简单的 Python 云原生组件运行起来！部署组件将上面快速生成的组件项目上传到 Git 仓库中，在 Rainbond 应用中选择从源代码开始添加组件，通过组件的的源码地址构建云原生组件。构建完成后，通过添加 "http访问策略" 来配置云原生组件的访问路由。接下来通过 PostMan 等接口调试工具，访问http://xxx.com/api/v1/demo?key=value(xxx替换成具体域名)，即可验证部署是否成功。（源码地址）部署存储组件在 Rainbond 应用中选择从源镜像开始构建组件，通过组件的的镜像地址构建存储 (数据库) 组件，在当前解决方案中，使用的是MongoDB数据库，因此将mongo作为镜像地址。存储 (数据库) 组件构建完成后，需要开通对内服务并同时将别名改成MONGODB，这样存储组件才能被其他组件的容器依赖和访问。通过上面的步骤，存储组件会自动创建两个依赖环境变量。在开发调试时，我们需要在本地设置这个 MongoDB 的环境变量，以 Linux 为例。$setMONGODB_HOST=127.0.0.1
$setMONGODB_PORT=27017如果是 Windows 环境，打开 "高级系统设置" 里的环境变量，直接可视化编辑即可。如果 MongoDB 设置了账号密码, 还需要额外设置下面两个环境变量:$setMONGODB_NAME=user1
$setMONGODB_PASSWORD=123456先给容器添加--auth启动参数命令, 再进入容器内部, 执行以下代码添加管理员账号密码.# 进入 admin$mongoadmin# 创建用户和密码$db.createUser({user:'admin',pwd:'123456',roles:[{role:"userAdminAnyDatabase",db:"admin"}]});# 验证一下对不对$db.auth("admin","123456");# 退出$exit# 修改密码(补充)$useadmin;$db.changeUserPassword('admin','88889999')设置一个普通用户.# 进入 admin$mongoadmin# 在 admin 数据库认证成功$db.auth('admin','123456');# 切换到 universal 数据库$useuniversal;# 不会在提示没有权限了$showcollections;# 为 universal 数据库添加了一个管理用户 user1$db.createUser({user:'user1',pwd:'123456',roles:[{role:"readWrite",db:"universal"}]});# 为 universal 数据库添加了一个只读用户 user2$db.createUser({user:'user2',pwd:'123456',roles:[{role:"read",db:"universal"}]});# 退出$exit# 修改权限(补充)$db.updateUser('user1',{pwd:'123456',roles:[{role:"readWrite",db:"universal"}]});数据库读写作为快速开始的部分，这里只展示最简单的增删改查操作。（源码地址）首先，我们接着上面的组件项目，在开头添加两行代码。fromflaskimportFlask,requestfromrainbond_python.parameterimportParameterfromrainbond_python.error_handlerimporterror_handlerfromrainbond_python.db_connectimportDBConnect# 添加的代码app=Flask(__name__)error_handler(app)db_books=DBConnect(db='demo',collection='books')# 添加的代码上面的DBConnect类是处理 MongoDB 读写行为的通用类，通过在初始化时，指定db和collection参数，我们可以连接到 MongoDB 的指定库、指定集合中。添加数据修改组件项目的POST部分代码。elifparameter.method=='POST':param=parameter.verification(checking=parameter.param_json,verify={'title':str,'author':str})new_id=db_books.write_one_docu(docu=param)return{'new_id':new_id}现在，我们往books集合中添加一本图书文档。上面Parameter类的verification()是一个非常实用的方法，它可以判断请求内容是否满足需求，我们可以放心使用通过verification()方法检查后的数据。而DBConnect类的write_one_docu()方法则用于写入单条文档，它接收一个字典，而恰好verification()方法返回的就是一个字典，所以可以直接作为数据写入。查询数据修改组件项目的GET部分代码。ifparameter.method=='GET':find_data=db_books.find_paging(parameter)returnfind_data现在，我们查询books集合中的所有图书文档。上面DBConnect类的find_paging()同样是一个特别实用的方法，如上面所见，你只需要把请求内容交给它，它会直接返回查询结果给你。你还可以试试多写入几个文档，然后请求http://127.0.0.1:5000/api/v1/demo?author=李杰接口，看看会有什么神奇的效果？更新数据修改组件项目的PUT部分代码。elifparameter.method=='PUT':param=parameter.verification(checking=parameter.param_json,verify={'id':str,'title':str,'author':str})update_result=db_books.update_docu(find_docu={'id':param['id']},modify_docu={'title':param['title'],'author':param['author']})returnupdate_result现在，我们更新指定id的books集合中的图书文档。上面DBConnect类的update_docu()方法用于更新文档，它会返回更新结果。删除数据修改组件项目的DELETE部分代码。elifparameter.method=='DELETE':param=parameter.verification(checking=parameter.param_json,verify={'id':str})delete_result=db_books.delete_docu(find_docu=param)returndelete_result现在，我们删除指定id的books集合中的图书文档。上面DBConnect类的delete_docu()方法用于删除文档，它会返回删除结果。建立组件依赖完成一个简单的图书管理组件后，我们要将组件部署到 Rainbond 平台，在 Rainbond 应用中选择切换到编辑模式进入依赖关系编辑页面，将计算组件依赖到存储组件即Mongo DB组件。重新构建计算组件即Demo Component组件，完成构建并滚动更新后，通过日志可以看到组件服务已经在正常运行了。需要注意：在 Rainbond 平台更改组件依赖关系后，相关的组件需要进行滚动更新。进阶异常处理通过error_handler()方法，可以将常用的4xx和5xx状态码的异常响应委托给rainbond-python处理，它会自动捕获这些异常并返回响应。......fromflaskimportFlask,requestfromrainbond_python.error_handlerimporterror_handler......app=Flask(__name__)error_handler(app)......跨域处理默认情况下error_handler()方法通过flask_cors库一键处理了服务端跨域问题，如果需要考虑安全问题，可以通过error_handler(app, simple_cors=False)取消跨域支持，同时，还可以重写跨域逻辑。......error_handler(app,simple_cors=False)fromflask_corsimportCORSCORS(app,resources={r'/.*':{'origins':'http://127.0.0.1:8888'}})......业务异常使用handle_abnormal()方法可以主动抛出业务逻辑异常，简单示例如下。......fromrainbond_python.toolsimporthandle_abnormal......@app.route('/api/v1/demo',methods=['GET'])defapi_demo():parameter=Parameter(request)ifparameter.method=='GET':handle_abnormal(message='异常信息~~~',status=400)......这样，当用户请求时，响应内容如下：{"code":400,"message":"异常信息~~~","server_time":20210220143830000,"host_name":"Z0jli2o0d2ymott0ggs6m","host_ip":"128.19.80.115"}还可以通过header参数字典设置响应头字典，通过other参数添加附加信息字典。handle_abnormal(message='2333~~~',status=400,other={'key1':'value1','key2':{'a':1}})这样就可以将更详细的提示信息告知用户，响应内容如下：{"code":400,"message":"2333~~~","server_time":20210220144625000,"host_name":"Z0jli2o0d2ymott0ggs6m","host_ip":"128.19.80.115","key1":"value1","key2":{"a":1}}当然，正常的业务逻辑也可以使用handle_abnormal()方法返回，但是考虑到业务逻辑的不确定性和复杂性，一般将作为异常逻辑的响应使用。数据备份mongo数据库的备份，是通过在rainbond平台mongo组件中安装插件的方式实现的，并且配合开源云存储服务，将备份数据上传至云端。插件安装步骤如下：插件参数说明必须参数：MONGODB_HOST ： 数据库地址MONGODB_PORT ： 数据库端口MONGODB_DB ： 需要备份的数据库名称FILE_DIR ： 备份数据本地保存相对路径（同时也是上传备份数据的接口参数之一）URL： 上传数据备份接口地址定时任务参数（只支持以下参数）DAY : 天循环DAY_OF_WEEK: 周期循环HOUR：小时循环MINUTE：分钟循环SECOND：秒循环相似用法可参考https://www.jianshu.com/p/4c5bc85fc3fd中2.1.3章节，第三点crontab触发器用法开源云存储服务安装（可使用rainbond直接安装即可使用）参考链接：https://www.laobuluo.com/2934.htmlParameter处理请求与响应参数的通用类。fromrainbond_python.parameterimportParameter获取请求参数通过Parameter类实例，可以获取以下信息：parameter.method: 请求类型parameter.headers: 请求头parameter.param_url: URL中传递的参数parameter.param_json: Json请求中的参数parameter.param_form: 表单请求中的参数所有信息均为字典类型，通过json.dumps()可以直接作为响应返回：@app.route('/api/1.0/demo',methods=['GET','POST','PUT','DELETE'])defapi_demo():parameter=Parameter(request)ifparameter.method=='GET':returnjson.dumps(parameter.param_url,ensure_ascii=False),200,[]elifparameter.method=='POST':returnjson.dumps(parameter.param_json,ensure_ascii=False),200,[]elifparameter.method=='PUT':returnjson.dumps(parameter.param_json,ensure_ascii=False),200,[]elifparameter.method=='DELETE':returnjson.dumps(parameter.param_json,ensure_ascii=False),200,[]校验参数内容通过Parameter类的verification()方法，可以判断参数字典是否符合要求：elifparameter.method=='POST':param=parameter.verification(checking=parameter.param_json,verify={'name':str,'age':int})其中checking参数是需要校验的参数字典，通常传递parameter.param_url、parameter.param_json或parameter.param_form。第二个verify参数则是校验内容字典，需要指定参数名和参数类型作为字典项。如果请求中包含可选参数，可以将该参数的名称及其默认值输入到optional参数中，例如可以设置age参数为空时，默认填充为18岁：parameter.verification(checking=parameter.param_json,verify={'name':str,'age':int},optional={'age':18})如果判断失败，则直接返回异常响应，响应体中包含明确的提示信息。默认情况下，str类型的 必选参数不能为空字符串，如果需要为空，可以通过null_value=True进行设置，或者将其作为可选参数处理。校验文件表单如果需要接收表单提交的文件对象，可以使用verification_file()方法对请求中的表单文件字段进行校验：elifparameter.method=='POST':param=parameter.verification(checking=parameter.param_form,verify={'id':str})param_file=parameter.verification_file(verify_field=['updata'])如上面的代码，如果请求中没有名为updata的表单文件字段，会直接返回异常信息。该方法与verification()方法可以同时使用。如果还需要判断上传文件的后缀名，可以通过verify_suffix参数进行配置：param_file=parameter.verification_file(verify_field=['updata'],verify_suffix=['jpg'])# 二者效果相同，但是列表类型可以同时指定多个后缀名称param_file=parameter.verification_file(verify_field=['updata'],verify_suffix=[['jpg']])该方法会返回werkzeug.datastructures.ImmutableMultiDict对象，即通过request.files获取到的对象，接下来就可以：通过param_file.get('xxxx')获取到文件对象通过param_file.get('xxxx').filename获取具体文件名称通过param_file.get('xxxx').save('/xxx/xxx.jpg')保存文件到本地DBConnect处理 MongoDB 读写行为的通用类。fromrainbond_python.db_connectimportDBConnectdb=DBConnect(db='db_name',collection='collection_name')分页查询支持GET和POST请求，使用非常简单，直接把Parameter类的实例传递给DBConnect类的find_paging()方法即可：@app.route('/api/1.0/demo',methods=['GET'])defapi_demo():parameter=Parameter(request)ifparameter.method=='GET':find_data=db.find_paging(parameter)returnfind_data,200,[]内部组件或外部客户端通过/api/v1/demo?$offset=0&$limit=15&name=sb即可访问，请求参数如下：$limit: 可选，指示页大小，从1开始计算，默认10条数据（单纯的数数，数几个就几个）$offset: 可选，指示记录起始位置，默认从0开始计算（代码逻辑，第几就是从数据库的第几条开始取文档）$orderby: 可选，排序规则（Eg:key1 desc,key2 asc），asc=升序、desc=降序$start_date: 可选，开始日期（区间查询），支持日期（2020-10-1）格式和时间戳（601481600）格式$end_date: 可选，结束日期（区间查询），同上，必须成对出现$date_type: 可选，区间查询的字段，默认为更新时间（update_time），可以设置成按创建时间（creation_time）查询<任意筛选字段>: 可选，任意筛选值响应字段如下：total: 查询到的文档总数items: 文档列表dummy_remove: 假删除文档数（不参与查询）由于技术原因，筛选字段目前不支持int型数据的模糊查询。同时$start_date和$end_date如果传递的是时间戳格式，能精确到秒。（更复杂的查询，可以将查询值提前处理成字典，这样可以实现一些非常规性的需求）写文档写入单个文档insert_dict={'name':'Xiao Ming','age':23}db.write_one_docu(docu=insert_dict)如果写入失败，会直接返回异常响应，如果成功则会返回新数据的_id值。写入多个文档insert_dict_list=[{'name':'Xiao Ming','age':23},{'name':'lao Yang','age':35}]db.write_many_docu(docu_list=insert_dict_list)如果写入失败，会直接返回异常响应，如果成功则会返回新数据的_id值的列表。文档是否存在examine_dict={'name':'Xiao Ming'}ifdb.does_it_exist(docu=examine_dict):print('Docu already exists')else:print('Docu does not exist')更新文档同样的，如果更新失败，也会直接返回异常响应。更新单个匹配文档find_dict={'name':'Xiao Ming'}modify_dict={'name':'Xiao Hong'}db.update_docu(find_docu=find_dict,modify_docu=modify_dict)更新全部匹配文档find_dict={'age':23}modify_dict={'name':'23 year old'}db.update_docu(find_docu=find_dict,modify_docu=modify_dict,many=True)该方法会返回一个包含matched_count和modified_count即匹配/影响数据条数的字典。更新文档-指定字段自增find_dict={'name':'xiao yang'}modify_dict={'age':1}db.update_docu_inc(find_docu=find_dict,modify_docu=modify_dict)表示age字段数字增加1，原先age=18，调用该方法后，age=19
该方法会返回一个包含matched_count和modified_count即匹配/影响数据条数的字典。删除文档删除文档分为真删除和假删除两种方式，通过delete_docu()方法实现，该方法会返回一个包含deleted_count和false_delete的字典。。真删除文档db.delete_docu(find_docu={'id':'60053fa139842d28d7563c6c'})假删除文档db.delete_docu(find_docu={'id':'60053fa139842d28d7563c6c'},false_delete=True)假删除操作会在对应的文档中添加一个remove_time字段，里面记录这个文档被移除的时间。批量删除文档db.delete_docu(find_docu={'id':{'$in':['111','222']}},many=True)查询文档通过find_docu()标准查询方法时，无论查询单个还是多个，返回均是list类型数据，没有匹配数据时返回空列表。查询单个匹配文档find_dict={'title':{'$regex':'标题'}}find_data_list=db.find_docu(find_dict=find_dict,many=False)print(find_data_list[0])查询全部匹配文档find_dict={'title':{'$regex':'标题'}}find_data_list=db.find_docu(find_dict=find_dict)forfind_datainfind_data_list:print(find_data)根据id查找文档fromrainbond_python.db_connectimportDBConnectdb=DBConnect('unitest_rainbond_python','test_db_connect')id=db.write_one_docu({'name':'LaoXu'})docu=db.find_docu_by_id(str(id))# 当id不存在时，默认会使用abort抛出异常fail_docu=db.find_docu_by_id('6008daa19223551b00548ded')# 可以将raise_err=False时，id不存在会返回Nonefail_docu=db.find_docu_by_id('6008daa19223551b00548ded',raise_err=False)该方法返回记录字典，且把'_id'转换为了str类型根据id列表查找文档fromrainbond_python.db_connectimportDBConnectdb=DBConnect('unitest_rainbond_python','test_db_connect')docu_list=db.find_docu_by_id_list(['6008daa19223551b00548ded','6008daa29223551b00548dee'])该方法返回记录字典列表，且把'_id'转换为了str类型。当所有id不存在时，返回[]根据字段去重查询文档num=find_docu_distinct('age')返回int类型数字，代表文档中age字段去重后的数量文件下载在网络上传输文件，目前主要有下载和流式传输两种方案，分别rainbond_python.download包的对应download_file()和download_flow()方法。普通下载通常用于文档文件（压缩包/PDF/TXT等文档），这种方式必须等全部内容传输完毕后，才能在本地机器打开：......fromrainbond_python.downloadimportdownload_file......ifparameter.method=='GET':download_response=download_file(file_path='C:/Users/xxx/Desktop',file_name='新建文本文档.txt'])returndownload_response......流式传输通常用于多媒体文件（视频/音频/直播流等场景），文件信息由服务器向用户计算机连续实时地传送，不必等到整个文件全部下载完毕，通常经过几秒或十几秒的启动延时即可打开：......fromrainbond_python.downloadimportdownload_flow......ifparameter.method=='GET':download_response=download_flow(file_path='C:/Users/xxx/Desktop',file_name='微视频.mp4'])returndownload_response......打包目录生成zip文件下载（在内存中打包）通常用于将现有目录文件，打包成zip文件，提供用户下载。打包zip文件数据在内存中完成，完成后从内存中读取二进制数据，并且回收内存。
参数save_zip表示本地是否存储打包zip文件，默认值为 False，为True时,打包文件保存在打包目录同级fromrainbond_python.downloadimportdownload_directory......ifparameter.method=='GET':download_response=download_directory(dir_path='C:/Users/xxx/project',zip_name='project.zip'],save_zip=False)returndownload_response......RedisConnect处理 Redis 读写行为的通用类。fromrainbond_python.redis_connectimportRedisConnectredis_connect=RedisConnect(db=0)权限认证认证中心组件之前，需要将组件依赖于认证中心、Redis和MongoDB组件，然后在业务代码中编写如下代码，完成接入：......fromflaskimportFlask,request,sessionfromrainbond_python.verify_tokenimportVerifyToken,set_token_session......# 注册权限信息到认证中心组件per_defaults=[{'session_key':'auth_xxxx','center_name':'组件名称','permission_name':'自定义权限','status':[0,1,2]},]token=VerifyToken(per_defaults=per_defaults)set_token_session(app,verify=token,per_defaults=per_defaults)......@app.route('/api/v1/demo',methods=['GET'])defapi_v1_demo():# 获取用户权限：0-1-2-4-8-16-32-64 (无权限-查看-新增-编辑-删除-下载-……)# Eg: [0, 1, 2]permission_list=session['auth_reports']['permission_list']ifparameter.method=='GET'and(1inpermission_list):# 用户有查询权限# 通过认证并符合权限后，可以执行业务逻辑psss......注册权限信息到认证中心组件的时候，center_name和permission_name建议都以中文命名，因为前端管理页面会直接读取这两个字段。session_key是写到会话中，建议使用英文命名。然后根据上面的注册内容，我们可以从会话中获取用户认证信息：session['auth_xxxx']['permission_list']: 0-1-2-4-8-16-32-64 (无权限-查看-新增-编辑-删除-下载-……)session['auth_xxxx']['is_all_data']: 前用户是否能查看全部数据session['real_name']: 账户真实姓名session['user_name']: 账户名称session['token_id']: 账户ID如果需要指定某些路由不参与认证，可以通过set_token_session方法的whitelist参数设置白名单，例如whitelist=['/api/v1/demo']则表示该路由可以随意被请求。通用方法handle_date()将2020-10-1或601481600即日期格式或时间戳格式的字符串，处理成 Python 的datetime.datetime数据：fromrainbond_python.toolsimporthandle_dateprint(handle_date(date='2020-10-1'))print(handle_date(date='2020-10-31',date_type='end'))通过date_type可以设置是日期的开始（start）还是一天的结束（end）时间。handle_db_dict()将 MongoDB 字典数据中的_id转换为str类型、时间转换成时间戳：query_dict=self.mongo_collection.find_one({'title':{'$regex':'标题1'}})handle_db_dict(query_dict)handle_db_to_list()将 MongoDB 的列表中的_id转换为str类型，并转换为字典列表（原db的id是ObjectId类型，转为json会报错）：fromrainbond_python.toolsimporthandle_db_to_listfromrainbond_python.db_connectimportDBConnectdeftest_handle_db_to_list():db=DBConnect('unitest_rainbond_python','test_parameter')old_list=db.mongo_collection.find({})new_list=handle_db_to_list(old_list)print('new_list is a list of dict',new_list)handle_time_difference()计算前端传递的两个时间戳之间，相差多少秒，返回float类型，解决前后端时间戳（前端的毫秒是整数位、Python的毫秒是小数位）差异问题：fromrainbond_python.toolsimporthandle_time_differencehandle_time_difference(start_timestamp=1614051008,end_timestamp=1614051008)开发与测试调试开发基础调试代码的demo.py即rainbond -c demo-component命令创建项目中的app.py文件，是一个可以快速开始的基础代码项目。在本地调试时，在demos目录下创建dev_xxxxx.py，并复制demo.py文件里的代码，并在里面调试rainbond_python目录下的代码。（本地创建的 dev_*.py 文件会被忽略，不会被提交），同时要在开头处添加下面代码，以调用基础包中的代码：......importsyssys.path.append('..')fromrainbond_python.parameterimportParameterfromrainbond_python.error_handlerimporterror_handlerfromrainbond_python.db_connectimportDBConnect......单元测试单元测试在 /tests/* 目录下执行单元测试$pytest参考Restful API: 具体的组件API开发标准12 Factor: 符合十二要素的才是云原生应用RainBond: 一个开源的云原生平台