Package: talos-api
talos project^ 1=======================[TOC]特性https://gitee.com/wu.jianjun/talos/tree/master/release项目是主要基于falcon和SQLAlchemy封装，提供常用的项目工具，便于用户编写API服务
项目提供了工具talos_generator，可以自动为您生成基于talos的api应用，并且目录结构基于python标准包管理基于falcon，高效使用SQLAlchemy作为数据库后端，快速切换数据库项目生成工具快速RESTful CRUD API开发filters，pagination，orders支持validation数据校验异步任务集成[celery]定时任务集成[celery]频率限制国际化i18n支持SMTP邮件、AD域、CSV导出、缓存等常用模块集成首先setup.py install 安装talos,运行talos生成工具生成项目项目生成安装talos后，会生成talos_generator工具，此工具可以为用户快速生成业务代码框架>talos_generator
>请输入项目生成目录：./
>请输入项目名称(英)：cms
>请输入生成类型[project,app,其他内容退出]：project
>请输入项目版本：1.2.4
>请输入项目作者：Roy
>请输入项目作者Email：roy@test.com
>请输入项目启动配置目录：./etc#此处填写默认配置路径，相对路径是相对项目文件夹，也可以是绝对路径>请输入项目DB连接串：postgresql+psycopg2://postgres:123456@127.0.0.1/testdb[SQLAlchemy的DB连接串]### 创建项目目录：./cms### 创建项目：cms(1.2.4)通用文件### 创建启动服务脚本### 创建启动配置：./etc/cms.conf### 创建中间件目录### 完成>请输入生成类型[project,app,其他内容退出]：app# 生成的APP用于编写实际业务代码，或手动编写### 请输入app名称(英)：user### 创建app目录：./cms/cms/apps### 创建app脚本：user### 完成>请输入生成类型[project,app,其他内容退出]：项目生成后，修改配置文件，比如**./etc/cms.conf的application.names配置，列表中加入"cms.apps.user"即可启动服务器进行调试**开发调试启动项目目录下的server/simple_server.py即可进行调试生产部署源码打包pipinstallwheel
pythonsetup.pybdist_wheel
pipinstallcms-1.2.4-py2.py3-none-any.whl启动服务：# Linux部署一般配置文件都会放在/etc/cms/下，包括cms.conf和gunicorn.py文件# 并确保安装gunicornpipinstallgunicorn# 步骤一，导出环境变量：exportCMS_CONF=/etc/cms/cms.conf# 步骤二，gunicorn--pid"/var/run/cms.pid"--config"/etc/cms/gunicorn.py""cms.server.wsgi_server:application"API开发引导基础开发步骤设计数据库略(talos要求至少有一列唯一性，否则无法提供item型操作)导出数据库模型talos中使用的是SQLAlchemy，使用表操作需要将数据库导出为python对象定义，这样做的好处是确定表结构，形成应用代码与数据库之间的版本对应便于编程中表达数据库操作，而并非使用字符串SQLAlchemy的多数据库支持，强大的表达能力pipinstallsqlacodegen
sqlacodegenpostgresql+psycopg2://postgres:123456@127.0.0.1/testdb--outfilemodels.py生成的models.py内容大致如下：# coding=utf-8from__future__importabsolute_importfromsqlalchemyimportStringfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()metadata=Base.metadataclassUser(Base):__tablename__='user'id=Column(String(36),primary_key=True)name=Column(String(63),nullable=False)当然这个导出操作如果在足够熟悉的情况下可以手动编写，不需要导出工具数据库模型类的魔法类将导出的文件表内容复制到cms.db.models.py中，并为每个表设置DictBase基类继承models.py文件中，每个表对应着一个class，这使得我们在开发业务处理代码时能明确表对应的处理，但在接口返回中，我们通常需要转换为json，因而，我们需要为models.py中的每个表的类增加一个继承关系，以便为它提供转换的支持处理完后的models.py文件如下：# coding=utf-8from__future__importabsolute_importfromsqlalchemyimportStringfromsqlalchemy.ext.declarativeimportdeclarative_basefromtalos.db.dictbaseimportDictBaseBase=declarative_base()metadata=Base.metadataclassUser(Base,DictBase):__tablename__='user'id=Column(String(36),primary_key=True)name=Column(String(63),nullable=False)classUserPhoneNum(Base,DictBase):__tablename__='user_phone'user_id=Column(String(63),nullable=False,primary_key=True)phone=Column(String(63),nullable=False,primary_key=True)description=Column(String(255),nullable=True)继承了这个类之后，不仅提供了转换接口json的能力，还提供了字段提取的能力，此处没有指定字段提取，则意味着默认使用表的所有字段（list和被其他表外键引用时默认不包含外键字段），如果需要自定义字段，可以在类中配置字段提取：classUser(Base,DictBase):__tablename__='user'# 用于控制获取列表时字段返回attributes=['id','name']# 用于控制获取详情时字段返回detail_attributes=attributes# 用于控制被其他资源外键引用时字段返回summary_attributes=['name']id=Column(String(36),primary_key=True)name=Column(String(63),nullable=False)指定attributes/detail_attributes/summary_attributes是非常有效的字段返回控制手段，减少返回的信息，可以减少服务器传输的压力，不仅如此，如果此处有外键relationship时，指定各attributes属性还可以有效的控制数据库对于外键的查询效率[^ 9]扩展阅读：一个较为常见的场景，某系统设计有数据库表如下：
User -> PhoneNum/Addresses, 一个用户对应多个电话号码以及多个地址
Tag，标签表，一个资源可对应多个标签
Region -> Tag，地域表，一个地域有多个标签
Resource -> Region/Tag，资源表，一个资源属于一个地域，一个资源有多个标签用models表示大致如下：classAddress(Base,DictBase):__tablename__='address'attributes=['id','location','user_id']detail_attributes=attributessummary_attributes=['location']id=Column(String(36),primary_key=True)location=Column(String(255),nullable=False)user_id=Column(ForeignKey(u'user.id'),nullable=False)user=relationship(u'User')classPhoneNum(Base,DictBase):__tablename__='phone'attributes=['id','number','user_id']detail_attributes=attributessummary_attributes=['number']id=Column(String(36),primary_key=True)number=Column(String(255),nullable=False)user_id=Column(ForeignKey(u'user.id'),nullable=False)user=relationship(u'User')classUser(Base,DictBase):__tablename__='user'attributes=['id','name','addresses','phonenums']detail_attributes=attributessummary_attributes=['name']id=Column(String(36),primary_key=True)name=Column(String(255),nullable=False)addresses=relationship(u'Address',back_populates=u'user',lazy=False,uselist=True,viewonly=True)phonenums=relationship(u'PhoneNum',back_populates=u'user',lazy=False,uselist=True,viewonly=True)classTag(Base,DictBase):__tablename__='tag'attributes=['id','res_id','key','value']detail_attributes=attributessummary_attributes=['key','value']id=Column(String(36),primary_key=True)res_id=Column(String(36),nullable=False)key=Column(String(36),nullable=False)value=Column(String(36),nullable=False)classRegion(Base,DictBase):__tablename__='region'attributes=['id','name','desc','tags','user_id','user']detail_attributes=attributessummary_attributes=['name','desc']id=Column(String(36),primary_key=True)name=Column(String(255),nullable=False)desc=Column(String(255),nullable=True)user_id=Column(ForeignKey(u'user.id'),nullable=True)user=relationship(u'User')tags=relationship(u'Tag',primaryjoin='foreign(Region.id) == Tag.res_id',lazy=False,viewonly=True,uselist=True)classResource(Base,DictBase):__tablename__='resource'attributes=['id','name','desc','tags','user_id','user','region_id','region']detail_attributes=attributessummary_attributes=['name','desc']id=Column(String(36),primary_key=True)name=Column(String(255),nullable=False)desc=Column(String(255),nullable=True)user_id=Column(ForeignKey(u'user.id'),nullable=True)region_id=Column(ForeignKey(u'region.id'),nullable=True)user=relationship(u'User')region=relationship(u'region')tags=relationship(u'Tag',primaryjoin='foreign(Resource.id) == Tag.res_id',lazy=False,viewonly=True,uselist=True)如上，user作为最底层的资源，被region引用，而region又被resource使用，导致层级嵌套极多，在SQLAlchmey中，如果希望快速查询一个列表资源，一般而言我们会在relationship中加上lazy=False，这样就可以让信息在一次sql查询中加载出来，而不是每次访问外键属性再发起一次查询。问题在于，lazy=False时sql被组合为一个SQL语句，relationship每级嵌套会被展开，实际数据库查询结果将是乘数级：num_resource*num_resource_user*num_resource_user_address*num_resource_user_phonenum*num_region*num_region_user_address*num_region_user_phonenum...较差的情况下可能回导致resource表只有1k数量级，而本次查询结果却是1000 * 1k级
通常我们加载resource时，我们并不需要region.user、region.tags、user.addresses、user.phonenums等信息，通过指定summary_attributes来防止数据过载（需要启用CONF.dbcrud.dynamic_relationship，默认启用），这样我们得到的SQL查询是非常快速的，结果集也保持和resource一个数量级增删改查的资源类在cms.db中新增resource.py文件，内容如下：# coding=utf-8from__future__importabsolute_importfromtalos.db.crudimportResourceBasefromcms.dbimportmodelsclassUser(ResourceBase):orm_meta=models.User_primary_keys='id'classUserPhoneNum(ResourceBase):orm_meta=models.UserPhoneNum_primary_keys=('user_id','phone')完成此项定义后，我们可以使用resource.User来进行用户表的增删改查，而这些功能都是ResourceBase默认提供的能力
可以看到我们此处定义了orm_meta和_primary_keys两个类属性，除此以外还有更多类属性可以帮助我们快速配置应用逻辑类属性默认值描述orm_metaNone资源操作的SQLAlchmey Model类[表]orm_poolNone指定资源使用的数据库连接池，默认使用defaultPool，实例初始化参数优先于本参数_dynamic_relationshipNone是否根据Model中定义的attribute自动改变load策略，不指定则使用配置文件默认(True)_dynamic_load_methodNone启用动态外键加载策略时，动态加载外键的方式，默认None，即使用全局配置（全局配置默认joinedload）joinedload 简便，在常用小型查询中响应优于subqueryloadsubqueryload 在大型复杂(多层外键)查询中响应优于joinedload_detail_relationship_as_summaryNone资源get获取到的第一层级外键字段级别是summary级，则设置为True，否则使用配置文件默认(False)_primary_keys'id'表对应的主键列，单个主键时，使用字符串，多个联合主键时为字符串列表，这个是业务主键，意味着你可以定义和数据库主键不一样的字段（前提是要确定这些字段是有唯一性的）_default_filter{}默认过滤查询，常用于软删除，比如数据删除我们在数据库字段中标记为is_deleted=True，那么我们再次list，get，update，delete的时候需要默认过滤这些数据的，等价于默认带有where is_delete = True_default_order[]默认排序，查询资源时被应用，('name', '+id', '-status'), +表示递增，-表示递减，默认递增_validate[]数据输入校验规则，为talos.db.crud.ColumnValidator对象列表一个validate示例如下：ColumnValidator(field='id',validate_on=['create:M']),ColumnValidator(field='name',rule='1, 63',rule_type='length',validate_on=['create:M','update:O']),ColumnValidator(field='enabled',rule=validator.InValidator(['true','false','True','False'])converter=converter.BooleanConverter(),validate_on=['create:M','update:O']),ColumnValidator可以定义的属性如下：属性类型描述field字符串字段名称rulevalidator对象 或 校验类型rule_type所需要的参数当rule是validator类型对象时，忽略 rule_type参数rule_type字符串用于快速定义校验规则，默认regex，可选类型有callback，regex，email，phone，url，length，in，notin，integer，float，typevalidate_on数组校验场景和必要性, eg. ['create: M', 'update:O']，表示此字段在create函数中为必要字段，update函数中为可选字段，也可以表示为['*:M']，表示任何情况下都是必要字段(默认)error_msg字符串错误提示信息，默认为'%(result)s'，即validator返回的报错信息，用户可以固定字符串或使用带有%(result)s的模板字符串converterconverter对象数据转换器，当数据被校验后，可能需要转换为固定类型后才能进行编程处理，转换器可以为此提供自动转换，比如用户输入为'2018-01-01 01:01:01'字符串时间，程序需要为Datetime类型，则可以使用DateTimeConverter进行转换orm_required布尔值控制此字段是否会被传递到数据库SQL中去aliases数组字段的别名，比如接口升级后为保留前向兼容，老字段可以作为别名指向到新名称上nullable布尔值控制此字段是否可以为None，默认FalseCRUD使用方式:resource.User().create({'id':'1','name':'test'})resource.User().list()resource.User().list({'name':'test'})resource.User().list({'name':{'ilike':'na'}},offset=0,limit=5)resource.User().count()resource.User().count({'name':'test'})resource.User().count({'name':{'ilike':'na'}})resource.User().get('1')resource.User().update('1',{'name':'test1'})resource.User().delete('1')resource.UserPhoneNum().get(('1','10086'))resource.UserPhoneNum().delete(('1','10086'))内部查询通过组装dict来实现过滤条件，filter在表达 == 或 in 列表时，可以直接使用一级字段即可，如
name等于test：{'name': 'test'}
id在1,2,3,4内：{'id': ['1', '2', '3', '4']}更复杂的查询需要通过嵌套dict来实现[^ 5]：简单组合：{'字段名称': {'过滤条件1': '值', '过滤条件2': '值'}}简单$or+组合查询：{'$or': [{'字段名称': {'过滤条件': '值'}}, {'字段名称': {'过滤条件1': '值', '过滤条件2': '值'}}]}简单$and+组合查询：{'$and': [{'字段名称': {'过滤条件': '值'}}, {'字段名称': {'过滤条件1': '值', '过滤条件2': '值'}}]}复杂$and+$or+组合查询：
{'$and': [
​               {'$or': [{'字段名称': '值'}, {'字段名称': {'过滤条件1': '值', '过滤条件2': '值'}}]},
​               {'字段名称': {'过滤条件1': '值', '过滤条件2': '值'}}
]}relationship复杂查询(>=v1.3.6)：假定有User(用户)，Article(文章)，Comment(评论/留言)表，Article.owner引用User， Article.comments引用Comment，Comment.user引用User查询A用户发表的文章中，存在B用户的评论，且评论内容包含”你好“Article.list({'owner_id': 'user_a', 'comments': {'content': {'ilike': '你好', 'user': {'id': 'user_b'}}}})过滤条件值类型含义likestring模糊查询，包含条件ilikestring同上，不区分大小写startsstring模糊查询，以xxxx开头istartsstring同上，不区分大小写endsstring模糊查询，以xxxx结尾iendsstring同上，不区分大小写inlist精确查询，条件在列表中ninlist精确查询，条件不在列表中eq根据字段类型等于ne根据字段类型不等于lt根据字段类型小于lte根据字段类型小于等于gt根据字段类型大于gte根据字段类型大于等于nlikestring模糊查询，不包含nilikestring同上，不区分大小写null任意是NULL，等同于{'eq': None}，null主要提供HTTP API中使用nnull任意不是NULL，等同于{'ne': None}，nnull主要提供HTTP API中使用hasanystring/list[string]JSONB专用包含任意key，如['a','b', 'c'] hasany ['a','d']hasallstring/list[string]JSONB专用包含所有key，如['a','b', 'c'] hasall ['a','c']withinlist/dictJSONB专用被指定json包含在内nwithinlist/dictJSONB专用不被指定json包含在内includelist/dictJSONB专用包含指定的jsonnincludelist/dictJSONB专用不包含指定的json过滤条件可以根据不同的数据类型生成不同的查询语句，varchar类型的in是 IN ('1', '2') , inet类型的in是<<=cidr一般类型的eq是col='value'，bool类型的eq是col is TRUE，详见talos.db.filter_wrapper业务api控制类api的模块为：cms.apps.user.apiresource处理的是DB的CRUD操作，但往往业务类代码需要有复杂的处理逻辑，并且涉及多个resource类的相互操作，因此需要封装api层来处理此类逻辑，此处我们示例没有复杂逻辑，直接沿用定义即可User=resource.UserUserPhoneNum=resource.UserPhoneNumCollection和Item ControllerController模块为：cms.apps.user.controllerController被设计分类为Collection和Item 2种，分别对应RESTFul的URL操作，我们先看一个常见的URL设计和操作POST/v1/users创建用户
GET/v1/users查询用户列表
PATCH/v1/users/1更新用户1的信息
DELETE/v1/users/1删除用户1的信息
GET/v1/users/1获取用户1的详情根据当前的URL规律我们可以吧创建和查询列表作为一个封装（CollectionController），而更新，删除，获取详情作为一个封装（ItemController），而同样的，对于这样的标准操作，talos同样提供了魔法般的定义classCollectionUser(CollectionController):name='cms.users'resource=api.UserclassItemUser(ItemController):name='cms.user'resource=api.Userroute路由映射route模块为：cms.apps.user.route提供了Controller后，我们还需要将其与URL路由进行映射才能调用，route模块中，必须有add_routes函数，注册app的时候会默认寻找这个函数来注册路由defadd_routes(api):api.add_route('/v1/users',controller.CollectionUser())api.add_route('/v1/users/{rid}',controller.ItemUser())配置启动加载app我们在引导开始时创建的项目配置文件存放在./etc中，所以我们的配置文件在./etc/cms.conf，修改..."application":{"names":["cms.apps.user"]},...启动调试或部署在源码目录中有server包，其中simple_server是用于开发调试用，不建议在生产中使用python simple_server.py测试API启动后我们的服务已经可以对外输出啦！那么我们的API到底提供了什么样的能力呢？我们以user作为示例展示创建POST /v1/users
Content-Type: application/json;charset=UTF-8
Host: 127.0.0.1:9002

{
    "id": "1",
    "name": "test"
}查询列表GET /v1/users
Host: 127.0.0.1:9002

{
    "count": 1,
    "data": [
        {
            "id": "1",
            "name": "test"
        }
    ]
}关于查询列表，我们提供了强大的查询能力，可以满足大部分的查询场景获取列表(查询)的接口可以使用Query参数过滤，使用过滤字段=xxx 或 字段__查询条件=xxx方式传递过滤条件eg.# 查询name字段等于abcname=abc# 查询name字段包含abcname__icontains=abc# 查询name字段在列表[a, b, c]值内name=a&name=b&name=c# 或name__in=a&name__in=b&name__in=c# 查询name字段在列表值内name[0]=a&name[1]=b&name[2]=c# 或name__in[0]=a&name__in[1]=b&name__in[2]=c同时支持全拼条件和缩写条件查询：全拼条件缩写条件含义N/A精确查询，完全等于条件，如果多个此条件出现，则认为条件在列表中containslike模糊查询，包含条件icontainsilike同上，不区分大小写startswithstarts模糊查询，以xxxx开头istartswithistarts同上，不区分大小写endswithends模糊查询，以xxxx结尾iendswithiends同上，不区分大小写inin精确查询，条件在列表中notinnin精确查询，条件不在列表中equaleq等于notequalne不等于lesslt小于lessequallte小于等于greatergt大于greaterequalgte大于等于excludesnlike模糊查询，不包含iexcludesnilike同上，不区分大小写nullnull是NULLnotnullnnull不是NULLhasanyhasanyJSONB专用包含任意key，如['a','b', 'c'] hasany ['a','d']hasallhasallJSONB专用包含所有key，如['a','b', 'c'] hasall ['a','c']withinwithinJSONB专用被指定json包含在内nwithinnwithinJSONB专用不被指定json包含在内includeincludeJSONB专用包含指定的jsonnincludenincludeJSONB专用不包含指定的json字段支持：普通column字段、relationship字段(single or list)、JSONB[^ 4]假设有API对应如下表字段classUser(Base,DictBase):__tablename__='user'id=Column(String(36),primary_key=True)name=Column(String(36),nullable=False)department_id=Column(ForeignKey(u'department.id'),nullable=False)items=Column(JSONB,nullable=False)department=relationship(u'Department',lazy=False)addresses=relationship(u'Address',lazy=False,back_populates=u'user',uselist=True,viewonly=True)classAddress(Base,DictBase):__tablename__='address'id=Column(String(36),primary_key=True)location=Column(String(36),nullable=False)user_id=Column(ForeignKey(u'user.id'),nullable=False)items=Column(JSONB,nullable=False)user=relationship(u'User',lazy=True)classDepartment(Base,DictBase):__tablename__='department'id=Column(String(36),primary_key=True)name=Column(String(36),nullable=False)user_id=Column(ForeignKey(u'user.id'),nullable=False)可以这样构造过滤条件/v1/users?name=小明/v1/users?department.name=业务部/v1/users?addresses.location__icontains=广东省/v1/users?addresses.items.key__icontains=temp/v1/users?items.0.age=60 # items = [{"age": 60, "sex": "male"}, {...}]/v1/users?items.age=60 # items = {"age": 60, "sex": "male"}v1.2.0  起不支持的column或condition会触发ResourceBase._unsupported_filter(query, idx, name, op, value)函数，函数默认返回参数query以忽略未支持的过滤(兼容以前版本行为)，用户可以自行重载函数以实现自定义行为v1.2.2  unsupported_filter会默认构造一个空查询集，即不支持的column或condition会致使返回空结果偏移量与数量限制查询返回列表时，通常需要指定偏移量以及数量限制eg.__offset=10&__limit=20代表取偏移量10，限制20条结果排序排序对某些场景非常重要，可以免去客户端很多工作量__orders=name,-env_code多个字段排序以英文逗号间隔，默认递增，若字段前面有-减号则代表递减PS：我可以使用+name代表递增吗？

  可以，但是HTTP URL中+号实际上的空格的编码，如果传递__orders=+name,-env_code，在HTTP中实际等价于__orders=空格name,-env_code, 无符号默认递增，因此无需多传递一个+号，传递字段即可字段选择接口返回中，如果字段信息太多，会导致传输缓慢，并且需要客户端占用大量内存处理__fields=name,env_code可以指定返回需要的字段信息，或者干脆不指定，获取所有服务器支持的字段进阶开发用户输入校验用户输入的数据，不一定是完全正确的，每个数据都需要校验后才能进行存储和处理，在上面已经提到过使用ColumnValidator来进行数据校验，这里主要是解释详细的校验规则和行为ColumnValidator被默认集成在ResourceBase中，所以会自动进行校验判断未定义_validate时，将不启用校验，信任所有输入数据未定义的字段在清洗阶段会被忽略校验的关键函数为ResourceBase.validate@classmethoddefvalidate(cls,data,situation,orm_required=False,validate=True,rule=None):"""验证字段，并返回清洗后的数据* 当validate=False，不会对数据进行校验，仅返回ORM需要数据* 当validate=True，对数据进行校验，并根据orm_required返回全部/ORM数据:param data: 清洗前的数据:type data: dict:param situation: 当前场景:type situation: string:param orm_required: 是否ORM需要的数据(ORM即Model表定义的字段):type orm_required: bool:param validate: 是否验证规则:type validate: bool:param rule: 规则配置:type rule: dict:returns: 返回清洗后的数据:rtype: dict"""validate_on为什么是填写：create:M或者update:M，因为validate按照函数名进行场景判定，在ResourceBase.create函数中，默认将situation绑定在当前函数，即 'create'，update同理，而M代表必选，O代表可选当前快速校验规则rule_type不能满足时，请使用Validator对象，内置Validator对象不能满足需求时，可以定制自己的Validator，Validator的定义需要满足2点：从NullValidator中继承重写validate函数，函数接受一个参数，并且返回True作为通过校验，返回错误字符串代表校验失败Converter同上多数据库支持 [^ 7]默认情况下，项目只会生成一个数据库连接池：对应配置项CONF.db.xxxx，若需要多个数据库连接池，则需要手动指定配置文件"dbs": {
    "read_only": {
        "connection": "sqlite:///tests/a.sqlite3"
    },
    "other_cluster": {
        "connection": "sqlite:///tests/b.sqlite3"
    }
},使用方式也简单，如下，生效优先级为：实例化参数 > 类属性orm_pool > defaultPool# 1. 在类属性中指定classUsers(ResourceBase):orm_meta=models.Usersorm_pool=pool.POOLS.read_only# 2. 实例化时指定Users(dbpool=pool.POOLS.readonly)# 3. 若都不指定，默认使用defaultPool，即CONF.db配置的连接池DB Hook操作简单hooks在db创建一个记录时，假设希望id是自动生成的UUID，通常这意味着我们不得不重写create函数：classUser(ResourceBase):orm_meta=models.User_primary_keys='id'defcreate(self,resource,validate=True,detail=True):resource['id']=uuid.uuid4().hexsuper(User,self).create(resource,validate=validate,detail=validate)这样的操作对于我们而言是很笨重的，甚至create的实现比较复杂，让我们不希望到create里面去加这些不是那么关键的代码，对于这些操作，talos分成了2种场景，_before_create, _addtional_create，根据名称我们能知道，它们分别代表create执行开始前：常用于一些数据的自动填充create执行后但未提交：常用于强事务控制的操作，可以使用同一个事务进行操作以便一起提交或回滚同理还有update，delete同样的list和count都有_addtional_xxxx钩子动态hooks以上的hooks都是类成员函数代码定义的，当使用者想要临时增加一个hook的时候呢，或者根据某个条件判断是否使用一个hook时，我们需要一种更动态的hook来支持，目前只有list和count支持此类hookslist,count的hook的函数定义为：function(query, filters)，需要return 处理后的queryeg. self.list(hooks=[lambda q,f: return q])自定义query在更复杂的场景下我们封装的操作函数可能无法达到目的，此时可以使用底层的SQLAlchemy Query对象来进行处理，比如在PG中INET类型的比较操作：一个场景：我们不希望用户新增的子网信息与现有子网重叠query=self._get_query(session)query=query.filter(self.orm_meta.cidr.op(">>")(subnet['cidr'])|self.orm_meta.cidr.op("<<")(subnet['cidr']))ifquery.one_or_none():raiseConflictError()会话重用和事务控制在talos中，每个ResourceBase对象都可以申请会话和事务，而且可以接受一个已有的会话和事务对象，在使用完毕后talos会自动帮助你进行回滚/提交/关闭，这得益与python的with子句u=User()withu.transaction()assession:u.update(...)# 事务重用, 可以查询和变更操作, with子句结束会自动提交，异常会自动回滚UserPhone(transaction=session).delete(...)UserPhone(transaction=session).list(...)withu.get_session()assession:# 会话重用, 可以查询UserPhone(session=session).list(...)缓存配置和使用默认配置为进程内存，超时60秒'cache':{'type':'dogpile.cache.memory','expiration_time':60}缓存后端支持取决于dogpile模块，可以支持常见的memcache，redis等如：redis"cache":{"type":"dogpile.cache.redis","expiration_time":6,"arguments":{"host":"127.0.0.1","password":"football","port":1234,"db":0,"redis_expiration_time":60,"distributed_lock":true}}使用方式fromtalos.commonimportcachecache.get(key,exipres=None)cache.set(key,value)cache.validate(value)cache.get_or_create(key,creator,expires=None)cache.delete(key)异步任务定义异步任务talos >=1.3.0 send_callback函数，移除了timeout，增加了request_context，为requests请求参数的options的字典，eg.{'timeout': 3}talos >=1.3.0 回调函数参数，移除了request和response的强制参数定义，保留data和url模板强制参数，如果callback(with_request=True)，则回调函数需定义如下，with_response同理@callback('/add/{x}/{y}', with_request=True):def add(data, x, y, request):​    passtalos >=1.3.0 被callback装饰的回调函数，均支持本地调用/快速远程调用/send_callback调用talos <1.3.0支持本地调用/send_callback调用本地调用：
add(data, x, y)，可以作为普通本地函数调用(注：客户端运行）
   
send_callback远程调用方式(x,y参数必须用kwargs形式传参)：
send_callback(None, add, data, x=1, y=7)
   
快速远程调用：
支持设置context，baseurl进行调用，context为requests库的额外参数，比如headers，timeout，verify等，baseurl默认为配置项的public_endpoint(x,y参数必须用kwargs形式传参)
   
test.remote({'val': '123'}, x=1, y=7)
test.context(timeout=10, params={'search': 'me'}).remote({'val': '123'}, x=1, y=7)
test.context(timeout=10).baseurl('http://clusterip.of.app.com').remote({'val': '123'}, x=1, y=7)建立workers.app_name.tasks.py用于编写远程任务
建立workers.app_name.callback.py用于编写远程调用
task.py任务示例fromtalos.commonimportceleryfromtalos.commonimportasync_helperfromcms.workers.app_nameimportcallback@celery.app.taskdefadd(data,task_id):result={'result':data['x']+data['y']}# 这里还可以通知其他附加任务,当需要本次的一些计算结果来启动二次任务时使用# 接受参数：task调用函数路径 & 函数命名参数(dict)# async_helper.send_task('cms.workers.app_name.tasks.other_task', kwargs={'result': result, 'task_id': task_id})# 异步任务中默认不启用数据库连接，因此需要使用远程调用callback方式进行数据读取和回写# 如果想要使用db功能，需要修改cms.server.celery_worker文件的代码,移除 # base.initialize_db()的注释符号# send callback的参数必须与callback函数参数匹配(request，response除外)# url_base为callback实际运行的服务端api地址，eg: http://127.0.0.1:9000# update_task函数接受data和task_id参数，其中task_id必须为kwargs形式传参# async_helper.send_callback(url_base, callback.update_task,#                             data,#                            task_id=task_id)# remote_result 对应数据为update_task返回的 res_afterremote_result=callback.update_task.remote(result,task_id=task_id)# 此处是异步回调结果，不需要服务器等待或者轮询，worker会主动发送进度或者结果，可以不return# 如果想要使用return方式，则按照正常celery流程编写代码returnresultcallback.py回调示例 (callback不应理解为异步任务的回调函数，泛指talos的通用rpc远程调用，只是目前框架内主要使用方是异步任务)fromtalos.commonimportasync_helper# data是强制参数，task_id为url强制参数(如果url没有参数，则函数也无需task_id)# task_id为url强制参数，默认类型是字符串(比如/callback/add/{x}/{y}，函数内直接执行x+y结果会是字符串拼接，因为由于此参数从url中提取，所以默认类型为str，需要特别注意)@async_helper.callback('/callback/add/{task_id}')defupdate_task(data,task_id):# 远程调用真正执行方在api server服务，因此默认可以使用数据库操作res_before,res_after=task_db_api().update(task_id,data)# 函数可返回可json化的数据，并返回到调用客户端去returnres_afterroute中注册回调函数，否则无法找到此远程调用fromtalos.commonimportasync_helperfromproject_name.workers.app_nameimportcallbackdefadd_route(api):async_helper.add_callback_route(api,callback.callback_add)启动worker
celery -A cms.server.celery_worker worker --autoscale=50,4 --loglevel=DEBUG -Q your_queue_name调用
add.delay('id', 1, 1)
会有任务发送到worker中，然后woker会启动一个other_task任务，并回调url将结果发送会服务端(如果API模块有统一权限校验，请注意放行）异步任务配置依赖：
​    库：
​        celery​    配置：{
        ...
        "celery": {
            "worker_concurrency": 8,
            "broker_url": "pyamqp://guest@127.0.0.1//",
            "result_backend": "redis://127.0.0.1",
            "imports": [
                "project_name.workers.app_name.tasks"
            ],
            "task_serializer": "json",
            "result_serializer": "json",
            "accept_content": ["json"],
            "worker_prefetch_multiplier": 1,
            "task_routes": {
                "project_name.workers.*": {"queue": "your_queue_name",
                                        "exchange": "your_exchange_name",
                                        "routing_key": "your_routing_name"}
            }
        },
        "worker": {
            "callback": {
                "strict_client": true,
                "allow_hosts": ["127.0.0.1"]
            }
        }
}定时任务^ 2talos中你可以使用原生celery的定时任务机制，也可以使用talos中提供的扩展定时任务(TScheduler)，扩展的定时任务可以在5s(可通过beat_max_loop_interval来修改这个时间)内发现定时任务的变化并刷新调度，从而提供动态的定时任务，而定时任务的来源可以从配置文件，也可以通过自定义的函数中动态提供原生celery的scheduler是不支持动态定时任务的使用原生celery定时任务因为talos配置项为json数据而无法提供复杂类型的schedule，当然也可以使用add_periodic_task来解决，但会降低我们使用的便利性这些问题在talos扩展定时任务中得以解决静态配置定时任务：使用最原始的celery定时任务配置，最快捷的定时任务例子^ 3："celery":{"worker_concurrency":8,"broker_url":"pyamqp://test:test@127.0.0.1//",..."beat_schedule":{"test_every_5s":{"task":"cms.workers.periodic.tasks.test_add","schedule":5,"args":[3,6]}}启动beat： celery -A cms.server.celery_worker beat --loglevel=DEBUG启动worker：celery -A cms.server.celery_worker worker --loglevel=DEBUG -Q cms-dev-queue可以看到每5s，beat会发送一个任务，worker会接收此任务进行处理，从而形成定时任务使用过原生celery的人可能看出这里存在的问题：crontab是对象，json配置是无法传递，只能配置简单的间隔任务，确实，缺省情况下由于配置文件格式的原因无法提供更高级的定时任务配置，所以talos提供了自定义的Scheduler：TScheduler，这个调度器可以从配置文件中解析interval、crontab类型的定时任务，从而覆盖更广泛的需求，而使用也非常简单："celery":{"worker_concurrency":8,"broker_url":"pyamqp://test:test@127.0.0.1//",..."beat_schedule":{"test_every_5s":{"task":"cms.workers.periodic.tasks.test_add","schedule":5,"args":[3,6]},"test_every_123s":{"type":"interval","task":"cms.workers.periodic.tasks.test_add","schedule":"12.3","args":[3,6]},"test_crontab_simple":{"type":"crontab","task":"cms.workers.periodic.tasks.test_add","schedule":"*/1","args":[3,6]},"test_crontab":{"type":"crontab","task":"cms.workers.periodic.tasks.test_add","schedule":"1,13,30-45,50-59/2 *1 * * *","args":[3,6]}}依然是在配置文件中定义，多了一个type参数，用于帮助调度器解析定时任务，此外还需要指定使用talos的TScheduler调度器，比如配置中指定:"celery": {
    "worker_concurrency": 8,
    "broker_url": "pyamqp://test:test@127.0.0.1//",
    ...
    "beat_schedule": {...}
    "beat_scheduler": "talos.common.scheduler:TScheduler"或者命令行启动时指定：启动beat： celery -A cms.server.celery_worker beat --loglevel=DEBUG -S talos.common.scheduler:TScheduler启动worker：celery -A cms.server.celery_worker worker --loglevel=DEBUG -Q cms-dev-queue除了type，TScheduler的任务还提供了很多其他的扩展属性，以下是属性以及其描述name:           string, 唯一名称
task:           string, 任务模块函数
[description]:  string, 备注信息
[type]:         string, interval 或 crontab, 默认 interval
schedule:       string/int/float/schedule eg. 1.0,'5.1', '10 *' , '*/10 * * * *' 
args:           tuple/list, 参数
kwargs:         dict, 命名参数
[priority]:     int, 优先级, 默认5
[expires]:      int, 单位为秒，当任务产生后，多久还没被执行会认为超时
[enabled]:      bool, True/False, 默认True
[max_calls]:    None/int, 最大调度次数, 默认None无限制
[last_updated]: Datetime, 任务最后更新时间，常用于判断是否有定时任务需要更新动态配置定时任务：TScheduler的动态任务仅限用户自定义的所有schedules
所有定时任务 = 配置文件任务 + add_periodic_task任务 + hooks任务，hooks任务可以通过相同name来覆盖已存在配置中的任务，否则相互独立使用TScheduler预留的hooks进行动态定时任务配置(推荐方式)：TScheduler中预留了2个hooks：talos_on_user_schedules_changed/talos_on_user_schedulestalos_on_user_schedules_changed钩子用于判断是否需要更新定时器，钩子被执行的最小间隔是beat_max_loop_interval(如不设置默认为5s)钩子定义为callable(scheduler)，返回值是True/Falsetalos_on_user_schedules钩子用于提供新的定时器字典数据钩子定义为callable(scheduler)，返回值是字典，全量的自定义动态定时器我们来尝试提供一个，每过13秒自动生成一个全新定时器的代码以下是cms.workers.periodic.hooks.py的文件内容importdatetimefromdatetimeimporttimedeltaimportrandom# talos_on_user_schedules_changed, 用于判断是否需要更新定时器# 默认每5s调用一次classChangeDetection(object):'''等价于函数，只是此处我们需要保留_last_modify属性所以用类来定义callabledef ChangeDetection(scheduler):...do something...'''def__init__(self,scheduler):self._last_modify=self.now()defnow(self):returndatetime.datetime.now()def__call__(self,scheduler):now=self.now()# 每过13秒定义定时器有更新ifnow-self._last_modify>=timedelta(seconds=13):self._last_modify=nowreturnTruereturnFalse# talos_on_user_schedules, 用于提供新的定时器字典数据# 在talos_on_user_schedules_changed hooks返回True后被调用classSchedules(object):'''等价于函数def Schedules(scheduler):...do something...'''def__init__(self,scheduler):passdef__call__(self,scheduler):interval=random.randint(1,10)name='dynamic_every_%ss'%interval# 生成一个纯随机的定时任务return{name:{'task':'cms.workers.periodic.tasks.test_add','schedule':interval,'args':(1,3)}}配置文件如下："celery":{..."beat_schedule":{"every_5s_max_call_2_times":{"task":"cms.workers.periodic.tasks.test_add","schedule":"5","max_calls":2,"enabled":true,"args":[1,3]}},"talos_on_user_schedules_changed":["cms.workers.periodic.hooks:ChangeDetection"],"talos_on_user_schedules":["cms.workers.periodic.hooks:Schedules"]},得到的结果是，一个每5s，最多调度2次的定时任务；一个每>=13s自动生成的随机定时任务使用官方的setup_periodic_tasks进行动态配置见celery文档截止2018.11.13 celery 4.2.0在定时任务中依然存在问题，使用官方建议的on_after_configure动态配置定时器时，定时任务不会被触发：GitHub Issue 3589@celery.app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(3.0, test.s('add every 3s by add_periodic_task'), name='add every 3s by add_periodic_task')

@celery.app.task
def test(arg):
    print(arg)而测试以下代码有效，可以使用以下方法：@celery.app.on_after_finalize.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(3.0, test.s('add every 3s by add_periodic_task'), name='add every 3s by add_periodic_task')

@celery.app.task
def test(arg):
    print(arg)频率限制controller & 中间件 频率限制主要用于http接口频率限制基本使用步骤：

- 在controller上配置装饰器
- 将Limiter配置到启动中间件

装饰器通过管理映射关系表LIMITEDS，LIMITEDS_EXEMPT来定位用户设置的类实例->频率限制器关系，
频率限制器是实例级别的，意味着每个实例都使用自己的频率限制器

频率限制器有7个主要参数：频率设置，关键限制参数，限制范围，是否对独立方法进行不同限制, 算法，错误提示信息, hit函数

:param limit_value: 频率设置：格式[count] [per|/] [n (optional)][second|minute|hour|day|month|year]
:param key_function: 关键限制参数：默认为IP地址(支持X-Forwarded-For)，自定义函数：def key_func(req) -> string
:param scope: 限制范围空间：默认python类完整路径，自定义函数def scope_func(request) -> string
:param per_method: 指定是否根据每个HTTP方法区分频率限制，默认True
:param strategy: 算法：支持fixed-window、fixed-window-elastic-expiry、moving-window
:param message: 错误提示信息：错误提示信息可接受3个格式化（limit，remaining，reset）内容
:param hit_func: 函数定义为def hit(controller, request) -> bool，为True时则触发频率限制器hit，否则忽略PS：真正的频率限制范围 = 关键限制参数(默认IP地址) + 限制范围(默认python类完整路径) + 方法名(如果区分独立方法)，当此频率范围被命中后才会触发频率限制静态频率限制(配置/代码)controller级的频率限制# coding=utf-8importfalconfromtalos.commonimportdecoratorsasdecofromtalos.commonimportlimitwrapper# 快速自定义一个简单支持GET、POST请求的Controller# add_route('/things', ThingsController())@deco.limit('1/second')classThingsController(object):defon_get(self,req,resp):"""Handles GET requests, using 1/second limit"""resp.body=('It works!')defon_post(self,req,resp):"""Handles POST requests, using global limit(if any)"""resp.body=('It works!')全局级的频率限制{"rate_limit":{"enabled":true,"storage_url":"memory://","strategy":"fixed-window","global_limits":"5/second","per_method":true,"header_reset":"X-RateLimit-Reset","header_remaining":"X-RateLimit-Remaining","header_limit":"X-RateLimit-Limit"}}基于中间件动态频率限制以上的频率限制都是预定义的，无法根据具体参数进行动态的更改，而通过重写中间件的get_extra_limits函数，我们可以获得动态追加频率限制的能力classMyLimiter(limiter.Limiter):def__init__(self,*args,**kwargs):super(MyLimiter,self).__init__(*args,**kwargs)self.mylimits={'cms.apps.test1':[wrapper.LimitWrapper('2/second')]}defget_extra_limits(self,request,resource,params):ifrequest.method.lower()=='post':returnself.mylimits['cms.apps.test1']频率限制默认被加载在了系统的中间件中，如果不希望重复定义中间件，可以在cms.server.wsgi_server中修改项目源代码：application=base.initialize_server('cms',...middlewares=[globalvars.GlobalVars(),MyLimiter(),json_translator.JSONTranslator()],override_middlewares=True)函数级频率限制fromtalos.commonimportdecoratorsasdeco@deco.flimit('1/second')deftest():pass用于装饰一个函数表示其受限于此调用频率
当装饰类成员函数时，频率限制范围是类级别的，意味着类的不同实例共享相同的频率限制，
如果需要实例级隔离的频率限制，需要手动指定key_func，并使用返回实例标识作为限制参数

:param limit_value: 频率设置：格式[count] [per|/] [n (optional)][second|minute|hour|day|month|year]
:param scope: 限制范围空间：默认python类/函数完整路径 or 自定义字符串.
:param key_func: 关键限制参数：默认为空字符串，自定义函数：def key_func(*args, **kwargs) -> string
:param strategy: 算法：支持fixed-window、fixed-window-elastic-expiry、moving-window
:param message: 错误提示信息：错误提示信息可接受3个格式化（limit，remaining，reset）内容
:param storage: 频率限制后端存储数据，如: memory://, redis://:pass@localhost:6379
:param hit_func: 函数定义为def hit(result) -> bool（参数为用户函数执行结果，若delay_hit=False，则参数为None），为True时则触发频率限制器hit，否则忽略
:param delay_hit: 默认在函数执行前测试频率hit(False)，可以设置为True将频率测试hit放置在函数执行后，搭配hit_func
                   使用，可以获取到函数执行结果来控制是否执行hit关于函数频率限制模块更多用例，请见单元测试tests.test_limit_func数据库版本管理修改models.py为最终目标表模型，运行命令：alembic revision --autogenerate -m "add table: xxxxx"备注不支持中文, autogenerate用于生成upgrade，downgrade函数内容，生成后需检查升级降级函数是否正确升级：alembic upgrade head降级：alembic downgrade basehead指最新版本，base指最原始版本即models第一个version，更多升级降级方式如下：alembic upgrade +2 升级2个版本alembic downgrade -1 回退一个版本alembic upgrade ae10+2 升级到ae1027a6acf+2个版本# alembic 脚本内手动执行sql语句op.execute('raw sql ...')单元测试talos生成的项目预置了一些依赖要求，可以更便捷的使用pytest进行单元测试，如需了解更详细的单元测试编写指导，请查看pytest文档python setup.py test可以简单从命令行输出中查看结果，或者从unit_test_report.html查看单元测试报告，从htmlcov/index.html中查看覆盖测试报告结果示例可以从talos源码的tests文件夹中查看$treetests
tests
├──__init__.py
├──models.py
├──test_db_filters.py
├──unittest.conf
├──unittest.sqlite3
└──...单元测试文件以test_xxxxxx.py作为命名Sphinx注释文档Sphinx的注释格式这里不再赘述，可以参考网上文档教程，talos内部使用的注释文档格式如下："""
    函数注释文档

    :param value: 参数描述
    :type value: 参数类型
    :returns: 返回值描述
    :rtype: `bytes`/`str` 返回值类型
    """安装sphinx在工程目录下运行：sphinx-quickstart docsroot path for the documentation [.]: docsProject name: cmsAuthor name(s): RoyProject version []: 1.0.0Project language [en]: zh_cnautodoc: automatically insert docstrings from modules (y/n) [n]: y可选的风格主题，推荐sphinx_rtd_theme，需要pip install sphinx_rtd_theme修改docs/conf.py# import os# import sys# sys.path.insert(0, os.path.abspath('.'))importosimportsyssys.path.insert(0,os.path.abspath('..'))importsphinx_rtd_themehtml_theme="sphinx_rtd_theme"html_theme_path=[sphinx_rtd_theme.get_html_theme_path()]生成apidoc sphinx-apidoc -o docs/ ./cms生成html：cd docsmake.bat html打开docs/_build/html/index.html国际化i18n同样以cms项目作为例子提取待翻译# 需要翻译项目的语言find/usr/lib/python2.7/site-packages/cms/-name"*.py">POTFILES.in# 需要翻译talos的语言find/usr/lib/python2.7/site-packages/talos/-name"*.py">>POTFILES.in# 提取为cms.poxgettext--default-domain=cms--add-comments--keyword=_--keyword=N_--files-from=POTFILES.in--from-code=UTF8合并已翻译msgmergecms-old.pocms.po-ocms.po翻译可以使用如Poedit的工具帮助翻译(略)编译发布Windows：使用Poedit工具，则点击保存即可生成cms.mo文件Linux：msgfmt --output-file=cms.mo cms.po将mo文件发布到/etc/{$your_project}/locale/{$lang}/LC_MESSAGES/{$lang}即配置项中的language日志配置配置指引全局日志配置为log对象，默认使用WatchedFileHandler进行日志处理，而文件则使用path配置，如果希望全局日志中使用自定义的Handler，则path参数是可选的全局日志通常可以解决大部分场景，但有时候，我们希望不同模块日志输出到不同文件中，可以使用：loggers，子日志配置器，其参数可完全参考log全局日志参数（除gunicorn_access，gunicorn_error），通过name指定捕获模块，并可以设置propagate拦截不传递到全局日志中不论全局或子级日志配置，都可以指定使用其他handler，并搭配handler_args自定义日志使用方式，指定的方式为：package.module:ClassName，默认时我们使用WatchedFileHandler，因为日志通常需要rotate，我们希望日志模块可以被无缝切割轮转而不影响应用，所以这也是我们没有将内置Handler指定为RotatingFileHandler的原因，用户需要自己使用操作系统提供的logrotate能力进行配置。以下是一个常用的日志配置，全局日志记录到server.log，而模块cms.apps.filetransfer日志则单独记录到transfer.log文件，相互不影响"log":{"gunicorn_access":"./access.log","gunicorn_error":"./error.log","path":"./server.log","level":"INFO","format_string":"%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s:%(lineno)d [-] %(message)s","date_format_string":"%Y-%m-%d %H:%M:%S","loggers":[{"name":"cms.apps.filetransfer","level":"INFO","path":"./transfer.log","propagate":false}]}其详细参数说明如下：路径类型描述默认值gunicorn_accessstring使用gunicorn时，access日志路径./access.loggunicorn_errorstring使用gunicorn时，error日志路径./error.loglog_consolebool是否将本日志重定向到标准输出Truepathstring日志路径，默认使用WatchedFileHandler，当指定handler时此项无效./server.loglevelstring日志级别(ERROR，WARNING，INFO，DEBUG)INFOhandlerstring自定义的Logger类，eg: logging.handlers:SysLogHandler，定义此项后会优先使用并忽略默认的log.path参数，需要使用handler_args进行日志初始化参数定义handler_argslist自定义的Logger类的初始化参数，eg: [][]format_stringstring日志字段配置%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s:%(lineno)d [-] %(message)sdate_format_stringstring日志时间格式%Y-%m-%d %H:%M:%Snamestring全局log中无此参数，仅用于loggers子日志配置器上，表示捕获的日志模块路径propagatebool全局log中无此参数，仅用于loggers子日志配置器上，表示日志是否传递到上一级日志输出True工具库带宽限速linux系统端口带宽限速：talos.common.bandwidth_limiter:BandWidthLimiter传输流带宽限速：未提供格式转换表格导出csv：talos.common.exporter:export_csvxlsx：未提供dict转xmltalos.core.xmlutils:toxmlxmlutils作为core模块的一份子，很重要的一点是：必须保证足够的扩展性，其提供了自定义类型的转换钩子test={'a':1,'b':1.2340932,'c':True,'d':None,'e':'hello <world />','f':{'k':'v','m':'n'},'g':[1,'2',False,None,{'k':'v','m':[1,2,3]}],'h':et.Element('root')}toxml(test,attr_type=True,hooks={'etree':{'render':lambdax:x.tag,'hit':lambdax:isinstance(x,et.Element)}})输出结果：默认情况下，所有未定义的类型，都会被default_render（实际上就是str() ）进行转换渲染hooks中定义的etree是作为xml的节点type属性输出，hit函数是用于判定定义的类型归属，render用于提取对象内容登录认证Ldap认证：talos.common.ldap_util:Ldap授权校验：talos.core.acl:Registryacl模块提供了扩展于RBAC的授权模式：policy概念对应角色(支持角色继承)，allow/deny对应一个权限规则，传统RBAC将角色与用户绑定即完成授权，但acl模块中，我们认为权限不是固定的，在不同的场景中，用户可以有不同的角色，来执行不同的动作，我们将场景定义为template(场景模板)，假定某用户在操作广州的资源时，可以获得最大的admin权限，而操作上海的资源时只有readonly角色权限access=Registry()access.add_policy('readonly')access.add_policy('manage',parents=('readonly',))access.add_policy('admin',parents=('manage',))access.allow('readonly','resource.list')access.allow('manage','resource.create')access.allow('manage','resource.update')access.allow('manage','resource.delete')access.allow('admin','auth.manage')# bind user1 with policies: (GZ, admin), (SH, readonly)# get current region: SHassertaccess.is_allowed([('GZ','admin'),('SH','readonly')],'SH','resource.create')isnotTrue# bind user2 with policies: (*, manage)# get current region: SHassertaccess.is_allowed([('*','manage')],'SH','resource.create')isTrue如上所示template(场景模板)是可以模糊匹配的，其默认匹配规则如下（匹配函数可以通过Registry更改）：PatternMeaning*matches everything?matches any single character[seq]matches any character inseq[!seq]matches any character not inseq如此一来我们便可以便捷实现一个基于场景的角色权限校验。SMTP邮件发送talos.common.mailer:Mailer实用小函数talos.core.utils配置项talos提供了一个类字典的属性访问配置类当属性是标准变量命名且非预留函数名时，可直接a.b.c方式访问否则可以使用a['b-1'].c访问(item方式访问时会返回Config对象)当属性值刚好Config已存在函数相等时，将进行函数调用而非属性访问！！！
保留函数名如下：set_options，from_files，iterkeys，itervalues，iteritems，keys，values，items，get，to_dict，_opts，python魔法函数比如{
​        "my_config": {"from_files": {"a": {"b": False}}}
​    }
无法通过CONF.my_config.from_files来访问属性，需要稍作转换：CONF.my_config['from_files'].a.b 如此来获取，talos会在项目启动时给予提示，请您关注[^ 8]预渲染项talos中预置了很多控制程序行为的配置项，可以允许用户进行相关的配置：全局配置、启动服务配置、日志配置、数据库连接配置、缓存配置、频率限制配置、异步和回调配置此外，还提供了配置项variables拦截预渲染能力[^ 6], 用户可以定义拦截某些配置项，并对其进行修改/更新（常用于密码解密）,然后对其他配置项的变量进行渲染替换，使用方式如下：{"variables":{"db_password":"MTIzNDU2","other_password":"..."}"db":{"connection":"mysql://test:${db_password}@127.0.0.1:1234/db01"}}如上，variables中定义了定义了db_password变量(必须在variables中定义变量)，并再db.connection进行变量使用(除variables以外其他配置项均可使用${db_password}变量进行待渲染)在您自己的项目的server.wsgi_server 以及 server.celery_worker代码开始位置使用如下拦截：importbase64fromtalos.coreimportconfig@config.intercept('db_password','other_password')defget_password(value,origin_value):'''value为上一个拦截器处理后的值（若此函数为第一个拦截器，等价于origin_value）origin_value为原始配置文件的值没有拦截的变量talos将自动使用原始值，因此定义一个拦截器是很关键的函数处理后要求必须返回一个值'''# 演示使用不安全的base64，请使用你认为安全的算法进行处理returnbase64.b64decode(origin_value)为什么要在server.wsgi_server 以及 server.celery_worker代码开始位置拦截？因为配置项为预渲染，即程序启动时，就将配置项正确渲染到变量中，以便用户代码能取到正确的配置值，放到其他位置会出现部分代码得到处理前值，部分代码得到处理后值，结果将无法保证一致，因此server.wsgi_server 以及 server.celery_worker代码开始位置设置拦截是非常关键的配置项路径类型描述默认值hoststring主机名当前主机名languagestring系统语言翻译enlocale_appstring国际化locale应用名称当前项目名locale_pathstring国际化locale文件路径./etc/localevariablesdict可供拦截预渲染的变量名及其值{}controller.list_size_limit_enabledbool是否启用全局列表大小限制Falsecontroller.list_size_limitint全局列表数据大小，如果没有设置，则默认返回全部，如果用户传入limit参数，则以用户参数为准Nonecontroller.criteria_key.offsetstringcontroller接受用户的offset参数的关键key值__offsetcontroller.criteria_key.limitstringcontroller接受用户的limit参数的关键key值__limitcontroller.criteria_key.ordersstringcontroller接受用户的orders参数的关键key值__orderscontroller.criteria_key.fieldsstringcontroller接受用户的fields参数的关键key值__fieldsoverride_defalut_middlewaresbool覆盖系统默认加载的中间件Flaseserverdict服务监听配置项server.bindstring监听地址0.0.0.0server.portint监听端口9001server.backlogint监听最大队列数2048logdict日志配置项log.log_consolebool是否将日志重定向到标准输出Truelog.gunicorn_accessstringgunicorn的access日志路径./access.loglog.gunicorn_errorstringgunicorn的error日志路径./error.loglog.pathstring全局日志路径，默认使用WatchedFileHandler，当指定handler时此项无效./server.loglog.levelstring日志级别INFOlog.handler[^ 7]string自定义的Logger类，eg: logging.handlers:SysLogHandler，定义此项后会优先使用并忽略默认的log.pathlog.handler_args[^ 7]list自定义的Logger类的初始化参数，eg: []log.format_stringstring日志字段配置%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s:%(lineno)d [-] %(message)slog.date_format_stringstring日志时间格式%Y-%m-%d %H:%M:%Slog.loggerslist模块独立日志配置，列表每个元素是dict: [{"name": "cms.test.api", "path": "api.log"}]log.loggers.namestring模块名称路径，如cms.apps.test，可以被重复定义使用log.loggers.levelstring参考log.levellog.loggers.pathstring参考log.pathlog.loggers.handler[^ 7]string参考log.handlerlog.loggers.handler_args[^ 7]list参考log.handler_argslog.loggers.propagate[^ 7]bool是否传递到上一级日志配置log.loggers.log_console[^ 7]bool参考log.log_consolelog.loggers.format_string[^ 7]string参考log.format_stringlog.loggers.date_format_string[^ 7]string参考log.date_format_stringdbdict默认数据库配置项，用户可以自行定义其他DB配置项，但需要自己初始化DBPool对象(可以参考DefaultDBPool进行单例控制)db.connectionstring连接字符串db.pool_sizeint连接池大小3db.pool_recycleint连接最大空闲时间，超过时间后自动回收3600db.pool_timeoutint获取连接超时时间，单位秒5db.max_overflowint突发连接池扩展大小5dbs[^ 7]dict额外的数据库配置项，{name: {db conf...}}，配置项会被初始化到pool.POOLS中，并以名称作为引用名，示例见进阶开发->多数据库支持dbcruddict数据库CRUD控制项dbcrud.unsupported_filter_as_emptybool当遇到不支持的filter时的默认行为，1是返回空结果，2是忽略不支持的条件，由于历史版本的行为默认为2，因此其默认值为False，即忽略不支持的条件Falsedbcrud.dynamic_relationshipbool是否启用ORM的动态relationship加载技术，启用后可以通过models.attributes来控制外键动态加载，避免数据过载导致查询缓慢Truedbcrud.dynamic_load_methodstring启用ORM的动态relationship加载技术后，可指定加载方式：joinedload，subqueryload，selectinload，immediateloadjoinedload：使用outer join将所有查询连接为一个语句，结果集少时速度最快，随着结果集和级联外键数量的增加，字段的展开会导致数据极大而加载缓慢subqueryload：比较折中的方式，使用外键独立sql查询，结果集少时速度较快，随着结果集和级联外键数量的增加，速度逐步优于joinedloadselectinload：类似subqueryload，但每个查询使用结果集的主键组合为in查询，速度慢immediateload：类似SQLAlchemy的select懒加载，每行的外键单独使用一个查询，速度最慢joinedloaddbcrud.detail_relationship_as_summarybool控制获取详情时，下级relationship是列表级或摘要级，False为列表级，True为摘要级，默认FalseFalsecachedict缓存配置项cache.typestring缓存后端类型dogpile.cache.memorycache.expiration_timeint缓存默认超时时间，单位为秒3600cache.argumentsdict缓存额外配置Noneapplicationdictapplication.nameslist加载的应用列表，每个元素为string，代表加载的app路径[]rate_limitdict频率限制配置项rate_limit.enabledbool是否启用频率限制Falserate_limit.storage_urlstring频率限制数据存储计算后端memory://rate_limit.strategystring频率限制算法，可选fixed-window，fixed-window-elastic-expiry，moving-windowfixed-windowrate_limit.global_limitsstring全局频率限制(依赖于全局中间件)，eg. 1/second; 5/minuteNonerate_limit.per_methodbool是否为每个HTTP方法独立频率限制Truerate_limit.header_resetstringHTTP响应头，频率重置时间X-RateLimit-Resetrate_limit.header_remainingstringHTTP响应头，剩余的访问次数X-RateLimit-Remainingrate_limit.header_limitstringHTTP响应头，最大访问次数X-RateLimit-Limitcelerydict异步任务配置项celery.talos_on_user_schedules_changedlist定时任务变更判断函数列表"talos_on_user_schedules_changed":["cms.workers.hooks:ChangeDetection"],celery.talos_on_user_scheduleslist定时任务函数列表"talos_on_user_schedules": ["cms.workers.hooks:AllSchedules"]workerdict异步工作进程配置项worker.callbackdict异步工作进程回调控制配置项worker.callback.strict_clientbool异步工作进程认证时仅使用直连IPTrueworker.callback.allow_hostslist异步工作进程认证主机IP列表，当设置时，仅允许列表内worker调用回调Noneworker.callback.name.%s.allow_hostslist异步工作进程认证时，仅允许列表内worker调用此命名回调NoneCHANGELOG1.3.6:更新：[crud] relationship的多属性 & 多级嵌套 查询支持修复：[utils] http模块的i18n支持1.3.5:修复：[crud] _addtional_update的after_update参数取值无效问题1.3.4:修复：[crud] update & delete时带入default_orders报错问题1.3.3:更新：[config] 优化config.item效率更新：[crud] 优化deepcopy导致的效率问题更新：[utils] 优化utils.get_function_name效率更新：[crud] 提供register_filter支持外部filter注册修复：[crud] any_orm_data返回字段不正确问题1.3.2:更新：[i18n] 支持多语言包加载，并默认第一语言（language: [en, zh, zh-CN,...]）修复：[crud] update relationship后返回的对象不是最新信息问题1.3.1:更新：[db] models动态relationship加载，效率提升(CONF.dbcrud.dynamic_relationship，默认已启用)，并可以指定load方式(默认joinedload)更新：[db] 支持设定获取资源detail级时下级relationship指定列表级 / 摘要级(CONF.dbcrud.detail_relationship_as_summary)更新：[test]动态relationship加载/装饰器/异常/缓存/导出/校验器/控制器模块等大量单元测试更新**[breaking]**：[controller]_build_criteria的supported_filters由fnmatch更改为re匹配方式由fnmatch更改为re匹配，提升效率，也提高了匹配自由度如果您的项目中使用了supported_filters=['name__*']类似的指定支持参数，需要同步更新代码如：['name__.*']如果仅使用过supported_filters=['name']类的指定支持参数，则依然兼容无需更改更新：[controller]_build_criteria支持unsupported_filter_as_empty配置(启用时，不支持参数将导致函数返回None)更新：[controller]增加redirect重定向函数修复：[controller]支持原生falcon的raise HTTPTemporaryRedirect(...)形式重定向修复：[util] xmlutils的py2/py3兼容问题修复：[schedule] TScheduler概率丢失一个max_interval时间段定时任务问题修复：[middleware]falcon>=2.0 且使用wsgiref.simple_server时block问题1.3.0:新增：[db] 多数据库连接池配置支持(CONF.dbs)新增：[worker] 远程调用快捷模式(callback.remote(...))新增：[cache] 基于redis的分布式锁支持(cache.distributed_lock)新增：[exception] 抛出异常可携带额外数据(raise Error(exception_data=...),  e.to_dict())更新：[exception] 默认捕获所有异常，返回统一异常结构更新：[exception] 替换异常序列化to_xml库，由dicttoxml库更换为talos.core.xmlutils，提升效率，支持更多扩展更新：[template] 生成模板：requirements，wsgi_server，celery_worker更新：[base] 支持falcon 2.0更新：[log] 支持自定义handler的log配置更新：[util] 支持协程级别的GLOABLS(支持thread/gevent/eventlet类型的worker)修复：[base] 单元素的query数组错误解析为一个元素而非数组问题修复：[util] exporter对异常编码的兼容问题修复：[crud] create/update重复校验输入值1.2.3:优化：[config] 支持配置项变量/拦截/预渲染(常用于配置项的加解密)1.2.2:优化：[util] 频率限制，支持类级/函数级频率限制，更多复杂场景优化：[test] 完善单元测试优化：[base] JSONB复杂查询支持优化：[base] 一些小细节1.2.1：见tags...[^ 4]: v1.2.0版本增加了__fields字段选择 以及 null, notnull, nlike, nilike的查询条件 以及 relationship查询支持
[^ 5]: v1.2.0版本新增$or,$and查询支持
[^ 6]: v1.2.3版本后开始支持
[^ 7]: v1.3.0版本新增多数据库连接池支持以及日志handler选项
[^ 8]: v1.3.0版本新增了Config项加载时的warnings，以提醒用户此配置项正确访问方式
[^ 9]: v1.3.1版本更新了relationship的动态加载技术，可根据设定的attributes自动加速查询(默认启用)