将pyramid、sqlalchemy、simplejson粘合在一起,以提供一个读写的、支持对象图的json api

py-liant的Python项目详细描述


简介

py liant是快速创建自以为是的restfulapi的助手库。 使用金字塔和sqlalchemy。它使用 一个稍微修改的对象图感知json结构,它紧密耦合 随着数据模型的曝光。

它是由内部项目的trip解决方案创建的,但我们认为它可能会证明 对一般消费有用。

restful api

基类假设api遵循rest约定 并提供crud([c]reate,[r]ead,[u]pdate,[d]elete)功能,或者 一个子集。它不会对端点做任何假设,这些端点是 仍然在用户代码中定义。对于 有效载荷,请参见修改后的json和crudview。

固执己见

但是,基类提供了一个自定义解析器 对于url字符串和api的结构有很大的自以为是。 这使得它可以轻松地部署在现有的sqlalchemy数据之上 结构,但缺点是可定制性较差。

修改后的json

orm数据模型并不总是树。任何超出 一定的复杂程度必然会导致映射深层数据 直接面向json的模型不是 可行。 在我们的第一次迭代中,我们通过手动分离来解决这个问题 结构中的json,但是任何手动过程都会很快变成一个时间 sink;它为客户端和服务器代码增加了许多复杂性。

py liant通过为内部保留两个关键字来解决图形感知问题 在json图中使用。需要从 json结构将获得一个带有生成值的特殊键\u id。工具书类 使用带有sigle的对象对对象进行编码匹配 引用对象的id。请注意,这只适用于sqlalchemy 模型对象。

例如,给定下面的模型声明:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))

下面的代码片段

frompy_liant.json_encoderimportJSONEncoderencoder=JSONEncoder(base_type=Base,check_circular=False,indent=4*' ')parent=Parent(id=1,data="parent object")parent.children.extend([Child(id=1,data="child 1"),Child(id=2,data="child 2")])print(encoder.encode(parent))

将输出

{"id":1,"data":"parent object","children":[{"id":1,"data":"child 1","parent":{"_ref":1},"_id":2},{"id":2,"data":"child 2","parent":{"_ref":1},"_id":3}],"_id":1}

编码器还将从sqlalchemy模型中提取元数据信息 支持序列化。它将只序列化列和关系 属性,这意味着它不会显示任何非sqlalchemy属性。它 也希望所有的关系都能被急切地加载,并避免触发 任何延迟加载的属性。也避免了延迟列。

相反,jsondecoder将变成一个简单编码的json。 构造并返回一个完整的图,具有潜在的循环或多个 参考资料,供应用程序使用。

解码器将生成一个结构 类是使用修补程序sqlalchemy base class进行修补的, 解码后的对象可用于修补现有或新的sqlalchemy模型 实例。

我们还提供了一对用于javascript的编码器/解码器函数 在pyliant.js中。

如何使用

在金字塔的配置块中,可以使用 以下内容:

frompy_liant.pyramidimportpyramid_json_renderer_factoryconfig.add_renderer('json',pyramid_json_renderer_factory(Base))

然后在任何@view_config()add_view()中使用renderer='json'

通过在金字塔的配置中添加以下内容,可以使用py liant的json解码器:

frompy_liant.pyramidimportpyramid_json_decoderconfig.add_request_method(pyramid_json_decoder,'json',reify=True)

因此,对于主体中包含json负载的任何请求,您都可以访问解码后的 jsonobject结构使用request.json

修补sqlalchemy模型的基类:

patch_sqlalchemy_base_class(Base)

添加视图谓词:

config.add_view_predicate('convert_matchdict',ConvertMatchdictPredicate)config.add_view_predicate('catchall',CatchallPredicate)

py liant还提供一个可调用的工厂来执行上述所有操作:

frompy_liant.pyramidimportincludeme_factoryconfig.include(includeme_factory(base_class=Base))# identical to includeme_factory(base_class=Base)(config)

crudview的具体用法示例 在参考文档中可以找到

参考

jsonobject

这个类是一个dict实现,它将所有字符串键公开为 性质。它消除了使用索引访问字典值的需要 表示法(request.json['prop']变成request.json.prop)。这个 jsondecoder返回此类的实例。

jsonencoder

asimplejson.jsonencoder实现,添加以下内容:

  • 日期时间日期时间对象转换为ISO8859字符串
  • 字节值转换为base64
  • 将pythonenum值限制为它们的名称,uuid.uuid
  • 跟踪SqlAlchemy模型(如果提供基类),如修改后的JSON中所述

构造函数参数:

JSONDecoder(request=None,base_type=None,**kwargs)

请求应为棱锥体请求对象。如果它用于 jsonguardprovider序列化的围栏。

base_type是sqlalchemy模型基类。如果没有提供 与sqlalchemy相关的功能被禁用。

kwargs被传递给simplejson.jsonencoder的构造函数

jsondecoder

返回 结果是,jsonobject并处理/逻辑 在修改后的json中描述。

构造函数参数:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
0

**kwargs被传递给simplejson.jsondecoder的构造函数。

金字塔json渲染器工厂

一个金字塔渲染器的工厂使用 jsonencoder。有关用法,请参见如何使用。

参数:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
1

基本类型分隔符传递给 构造器。分隔符的默认值是为了最小化负载 跳过任何不必要的空格来调整大小。

wsgi-iter可以通过传递iterable来优化json的呈现 直接到wsgi层。默认情况下,渲染器直接在 金字塔响应对象。当激活的棱锥体不能再处理错误时 重定向序列化期间引发的执行选项。

金字塔JSON解码器

这是一个功能,可以添加到金字塔使用 配置添加请求方法。有关用法,请参见如何使用。

修补程序sqlalchemy基础类

这是添加方法的函数 将更改应用到sqlalchemy的基类中

monkeypatch:obj.应用更改

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
2

一旦sqlalchemy的基类使用 patch sqlalchemy_base_class所有模型实例都将获得 可用于应用修补程序的方法。这可以直接使用,但大多数 如果您使用crudview和/或 catchallview,您不必。

该方法将在所需的任何深度应用更改。它转换数据类型 基于从sqlalchemy中提取的元数据。它处理关系,两者 集合和实例,通过跟踪和比较json中提供的主键。如果需要,它将添加新实例。

对于没有关系的对象,它将data中的值应用到 在obj中对应的列属性。没有覆盖属性值 除非在数据对象中指定。

如果对象具有关系,则数据对象可以深入到这些关系中。为了 集合关系apply_changes方法希望所有对象 在相应的数组中提供,至少使用它们的主键 现在。如果数组的成员不提供主键,则假定 成为一个新的例子。如果无法跟踪对象集合的成员 返回数据中的数组成员,它将从集合中移除。

如果子体的主键是包含 外键中的列调用方可以提供部分主键和 py liant将根据与 父母,

如果提供了实现 jsonguardprovider,它将用于安全围栏 修补。

crudview

该类为给定的模型类提供CRUD功能。你可以 根据应用程序的需要配置路由和视图,但是 推荐方式如下:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
3

这足以为类型为的对象提供完整的读写端点 父级

使用get/parent/1http/1.1检索id=1的父项。它应该会回来 大致如下:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
4

使用

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
5

在id=1的父实例中更新数据。

过帐到/parent而不是/parent/1将创建一个新实例 更新现有的。

删除/parent/2http/1.1将删除id=2的父项。

最后,get/parent http/1.1将提供 数据库。

对于列表端点,将返回以下响应:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
6

克鲁德维尤

通过get parameterspagepagesize支持分页(即get/parent?页码=3&pagesize=20)。

为所有列属性提供隐式筛选器和排序。假设 列属性iddata对于类用户,以下过滤器将是 添加到self.filters(在上面的示例用法中,在构造期间,请参见 auto_filters()调用:id,id_lt,id_le,id_gt,id_ge,data,data_lt, 数据,数据,数据,数据。筛选器[字段名][运算符] 使用小于、小于或等于、大于 大于或等于并包含运算符。最后一个是自动生成的 仅用于字符串列属性。

还将添加自动过滤器(在上面的示例用法中,请参见调用 auto_order())两个字段。

列表端点中的过滤是这样完成的:get/parent?data_like=对象。 可以应用多个过滤器,即get/parent?id_lt=10&id_gt=5

排序是通过使用get参数order完成的,即get/parent?顺序=数据。可以应用多个排序表达式,即 顺序=数据,id。换句话说,顺序中传递的值是逗号分隔的 排序键列表。每个排序键也接受降序修饰符, 即order=data+desc,id

排序和筛选键也可以手动定义。在上面的使用示例中,我们可以手动定义一些筛选器和排序:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
7

这样做显然更费劲,但允许您定义自定义筛选器或排序表达式。

该实现假定request.dbsession是一个返回 对模型有效的sqlalchemy数据库会话。

convertMatchDictPredicate

如果棱锥体已配置为使用此谓词,如如何使用 使用可以避开转换matchdict参数的需要。

金字塔的url 调度< 文档页显示以下URL MatchDict转换示例:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
8

这段代码确保除非谓词执行,否则路由将不匹配 成功(返回true)并且视图将看到键的整数值 请求.matchdict中的。虽然这很有用 不幸的是不推荐使用的功能。Sice金字塔-1.5你将得到 在路由或视图中使用自定义谓词时,将发出弃用警告。

为了用支持的机制替换此功能,我们实现了 泛型新样式路由谓词类。在你的路线中使用这个类 首先必须按照如何使用中所述配置它。然后在 上一节中视图为routeparent\u pk配置的示例 更改如下:

fromsqlalchemy.ormimportrelationship,backreffromsqlalchemyimportColumn,Integer,Text,ForeignKeyfromsqlalchemy.ext.declarativeimportdeclarative_baseBase=declarative_base()classParent(Base):__tablename__='parent'id=Column(Integer,primary_key=True)data=Column(Text)classChild(Base):__tablename__='child'id=Column(Integer,primary_key=True)parent_id=Column(ForeignKey(Parent.id))data=Column(Text)parent=relationship(Parent,backref=backref('children'))
9

请注意,在旧的custom_谓词中 matchdict参数是在路由级别完成的,新样式的路由谓词 无法访问matchdict。因此,我们必须使用视图谓词 实现同样的目标。

在这些更改之后,您不再需要在 identity_filter()方法。您还可以避免捕获valueerror 例外。

catchallpredicate

这是一个支持谓词,可与catchallview一起使用。它 假设路由包含{catchall:.}(不是 *catchall},因为star格式从 匹配)然后在内部分析并转换为更适合catchAllView类的值。

catchallview

这是crudview类的一个扩展,添加了对 更丰富的路由格式基于由catchAllPredicate完成的内部解析,并且能够:

  • 在一个地方公开多个实体类型
  • 提供路线装载提示中指定的任意紧急装载深度
  • 钻取动态和静态关系
  • 提供切片语法,以便于分页

使用这个类:

frompy_liant.json_encoderimportJSONEncoderencoder=JSONEncoder(base_type=Base,check_circular=False,indent=4*' ')parent=Parent(id=1,data="parent object")parent.children.extend([Child(id=1,data="child 1"),Child(id=2,data="child 2")])print(encoder.encode(parent))
0

此代码足以公开如下路由:

  • get/parentget/child列出所有父级或子级
  • get/parent@1get/child@1获取id=1的父级或id=1的子级
  • post/parentpost/child添加新父级
  • post/parent@1更新id=1的父属性
  • 删除/parent@1删除/child@1删除id=1的父级或 ID=1

换句话说,可以从 单点。

提示语法

但是从应用程序的角度来看 child在根级别可能不是什么有用的东西,换句话说 可能希望您的api将子项视为与父项紧密绑定。卡塔查维尤 允许您使用get/parent@1:*children一次性获取父实体和所有子实体。CatchallView将看到路线的一部分 作为父实体的加载提示列表的列字符之后。在 在这种情况下,它会在查询中附加一个selectinload(parent.children)选项。

这些提示还允许您通过 推迟他们。即,如果您在父节点和调用方上添加了blob属性 为了避免检索,他们可以调用get/parent@1:-blob。 相反,如果blob属性在模型中被mared为延迟列 声明,但调用者希望它包含在他们可以包含的响应中 通过调用get/parent@1:+blob

如果我们还为子实体添加了blob列(假设它是 代码中的延迟列),调用者可以得到包含所有子项的父项 通过调用get/parent@1:*children(+blob)为每个对象包含blob。 通过逗号分隔可以提供多个提示。这也是事实 对于关系提示:

  • get/parent@1:-blob,*children表示"loadparent和allchildren 包含并延迟加载列parent.blob"
  • get/parent@1:-blob,*子节点(+blob,-blob2,*第二个父节点)表示"加载父节点" 包含所有的子节点后,延迟列parent.blobchild.blob2并 取消定义columnchild.blob。对于每个子对象,还加载关系child.second_parent

提示可以具有任意深度。每个关系提示都可以有提示 指的是这种关系的实体。

提示也适用于列出请求:get/parent:*childrenwill 有效地检索所有父级和所有关联的子级。

请注意:动态关系属性不能是 关系提示。

向下钻取支架

如果打电话的人只想找回父母的孩子不知道他们是谁 可以调用get/parent@1/children。路线的最后一点不是提示, 它是一个钻取说明符。这将构造一个查询,检索所有子项 对于id=1的父级,通过读取关系的外键约束 父级.子级

向下钻取同时支持正常关系属性和动态关系属性 关系属性。它自动确定目标属性是否为 列表或单个实体(即get/child@1/parent也可以)。所有提示 前提是必须在钻取说明符之后,它们将引用 关系中的实体被钻入。例如在请求中 get/parent@1/children:+blob提示将延迟加载列 子.blob

如果正在钻取的属性是集合all,则过滤、排序和 分页注意事项适用。

集合中的单个元素

如果请求通过 钻取或引用实体集合,因为它 不包含主键说明符调用方可以选择单个项 从列表中使用下标符号。例如,get/parent@1/children[0]将检索parent.children的第一个子 收集。应用过滤和排序 首先,

过滤、排序、分页

过滤、排序和分页的应用如 crudview部分。仅使用auto_filtersauto_order。 即将支持自定义表达式。

也支持由crudview支持的分页,但是 上一节中描述的下标符号可用于 切片:get/parent[0:10]?order_by=data+desc检索前10个父节点 降序实体数据顺序。

jsonguardprovider

出于安全考虑,此库提供的灵活性可以是 有害的。模型类可以包含对需要 从读取它们(当使用 就更新它们而言(任何 插入/更新方法)。

jsonguardprovider接口允许您为四个 领域:

  • 方法guardserialize允许您控制获得多少信息 序列化为json
  • 方法guardupdate允许您控制可以写入 实体只要obj.apply_changes()get called
  • 方法guardhints允许您控制catchallview 允许使用提示
  • 方法guardrilldown允许您控制可以是什么属性
  • 钻取到viacatchallview

使用jsonguardprovider在金字塔中实现这个接口 上下文 然后将其连接到路线并使用add_route工厂查看

例如:

frompy_liant.json_encoderimportJSONEncoderencoder=JSONEncoder(base_type=Base,check_circular=False,indent=4*' ')parent=Parent(id=1,data="parent object")parent.children.extend([Child(id=1,data="child 1"),Child(id=2,data="child 2")])print(encoder.encode(parent))
1

搜索路径设置器

这是postgresql特有的附加功能,可用于设置架构 搜索所有新创建的数据库连接的路径。它的实现方式是 sqlalchemypoollistener(自0.7版以来已弃用)。替代品 使用当前正在开发的现代事件API。

< V >你不太可能在你的项目中使用这个类,除非你 需要使用具有可配置架构的多租户数据库。

Enumattrs和pythonenum

pythonenum是sqlalchemy.types.enum的自定义实现,即 在PostgreSQL中用于声明命名枚举类型。

用法:

frompy_liant.json_encoderimportJSONEncoderencoder=JSONEncoder(base_type=Base,check_circular=False,indent=4*' ')parent=Parent(id=1,data="parent object")parent.children.extend([Child(id=1,data="child 1"),Child(id=2,data="child 2")])print(encoder.encode(parent))
2

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
java ldap连接池超时属性未按预期工作   java Eclipse Google插件不会为web应用程序启动服务器   将工作应用程序从一台pc复制到另一台pc的java安全   安卓如何查找和调试实际代码行中的Java错误:致命异常:Java。lang.IndexOutOfBoundsException:   jms将应用程序Java连接到websphere MQ   java如何遍历对象列表并分配子对象?   java我的代码有什么问题吗?为什么压缩和解压缩速度比其他应用程序慢?   java表达式的类型必须是数组类型,但它被解析为Object   模拟协议socketjava   使用googleappengine的java缓存   java为什么对象引用父类的值而不是它被分配到的类?   删除位置华为工具包安卓 studio时发生java错误   unix执行远程ssh命令“which java”(JSch java)   Dropbox Djinni Java接口与类扩展   java条形码扫描完成后是否有事件?   安卓 GCM Java服务器:发送带有重音字符的消息   java使用PDF框从PDF中读取条形码   私有静态最终更改的java值