一种简单的api查询语言

apiql的Python项目详细描述


apiql-一种用于restful服务的简单api查询语言

apiql是一种简单的域语言,供api使用者以简洁、强大、url友好的方式表达rest资源过滤条件。

安装

可以使用pip安装和更新apiql:

pip安装apiql

API查询语言

api查询语言提供了一组谓词和表达式,允许api提供者和使用者构建复杂的资源查询。虽然apiql语法在很大程度上受sql启发,但它的设计与技术无关。apiql被转换成抽象的标准树,并且可以转换成后端特定的功能。

apiql查询语法

apiql查询是一组基本谓词,然后可以使用连接()或析取()组合成表达式。

例如,假设我们有rest api公开电影信息:

wget-q-o-'http://awso.me/api/movies'

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]

apiql查询基础知识

在最基本的形式中,apiql查询只是一个谓词:

wget-q-o-'http://awso.me/api/movies?filter=title=="巨蟒与圣杯"'

它只过滤"monty python and the holy grails"资源。(整个apiql查询包含在单个url查询参数中;请注意,apiql使用=运算符实现相等,而不是=)。

注意:apiql被设计成尽可能友好的url,但是所有apiql查询都应该是url编码的。在本文档中,我们使用wget因为它默认情况下url编码所有url;curl在本例中有一点更神秘的语法:curl-g--data urlencode"filter=[my apiql query]"http://awso.me/api/movies

apiql谓词

实际上,api使用者需要的不仅仅是简单的谓词。APIQL支持以下谓词集:

  • =
  • < L> >代码>!=,
  • >;
  • >;=
  • <;
  • <;=
  • like-等效的类sql运算符,但是不需要显式添加%
  • 我喜欢-类似于,
  • 的不区分大小写版本
  • not like-等效的sql notlike运算符,
  • notilike-不区分大小写的版本
  • starts with-相当于sql starts with operator,
  • istartswith-不区分大小写版本的startswith
  • ends with-相当于sql以,
  • 结尾
  • IEndswith-不区分大小写版本的IEndswith
  • 包含-alias tolike
  • notcontains-别名为notlike
  • icontains-不区分大小写版本的包含
  • inotcontains-不区分大小写的版本notcontains
  • 输入-相当于SQL输入运算符,
  • not in相当于sql not in运算符。

例如,查询:

wget-q-o-'http://awso.me/api/movies?filter=title我喜欢"神圣"'

将返回与"holy"匹配的所有电影:"monty python and the holy grails","holy money"和可能与"holy"匹配的一堆其他电影。

以及查询:

wget-q-o-'http://awso.me/api/movies?过滤器=发布年份>;=1975'

将返回1975年或以后发行的所有电影。

查询文本

文字是值,可以在谓词的右边。到目前为止,我们已经看到字符串("神圣")和数字文字(1975年)。apiql也支持许多其他文本:

  • 字符串-所有字符串文本都是Unicode的,并且遵循与JSON字符串文本相同的规则s.apiql字符串总是用双引号括起来(例如,这是一个字符串:"这是一个字符串",但是:"不是字符串"不是),并转义("电影:\"电影")。
  • 数字-基本上是整数和浮点数:发布年份!=2003评级>;3.3甚至评级>;-1.6e-35
  • 布尔值-truefalse被转换为特定于平台的布尔值。示例用法:ingored!=假
  • nil-specialnullliteral被转换为特定于平台的literal,例如:genres!=空
  • datetime-表示日期时间的文字:created_datetime>;=日期时间("2019-05-01t08:00:00.527181+00:00")。开箱即用apiql支持iso-8601日期时间格式(但是,可以自定义此行为)。
  • 元组-表示innotin子句中的一系列值:release\u year notin(1975,2011)。元组可以包含其他文本的coma分隔列表:发布日期in(flase,null,datetime("datetime("1975-01-01t00:00:00.000000+00:00"))

组合查询

通过对原子谓词进行分组(用分隔),可以将查询组合成更复杂的表达式。

例如:

wget-q-o-'http://awso.me/api/movies?filter=title ilike"holy";release_year>;1975;忽略!=空'

此查询中的谓词被解释为连词),返回1975年以后发行的所有与标题匹配的"holy"电影,这些电影未标记为忽略

逻辑表达式

apiql支持逻辑连接)和析取);它们都可以嵌套谓词:和(criteria(;criteria)*)或(criteria(;criteria)*)

下面的查询,相当于上一个查询:

wget-q-o-'http://awso.me/api/movies?filter=和(标题Ilike"Holy";发布年份>;1975;忽略!=真)

不过,这一个:

wget-q-o-'http://awso.me/api/movies?filter=或(标题Ilike"Holy";发布年份>;1975;忽略!=真)

将返回所有标题与1975年后发行的"神圣的"匹配且未被忽略的电影。

连接和析取可以嵌套。假设我们希望筛选分级大于7或源为"imdb"的电影,但是我们只希望筛选未被忽略的资源:

wget-q-o-'http://awso.me/api/movies?filter=和(或(rating>;7;source="imdb");忽略!=flase)

分析APIQL查询

到目前为止,还不错。现在,api数据存储如何解释apiql查询呢?apiql查询在内部转换为python数据结构(语法树),由crtieria类表示。

条件以及谓词连接分离完全表示已解析的查询树。

条件类聚合条件的列表

条件只是抽象的原子条件元素;它要么是:

  • 简单的谓词原子逻辑表达式(例如 谓词('title','==','apocalypse now')用于查询title=="apocalypse now"
  • 连接这也是一个逻辑运算符,它带有一组谓词连接([predicate('title','==','apocalypse now'),predicate('release_year','>;',1975])用于查询和(title=="apocalype now";release_year>;1975)
  • 析取-逻辑运算符

用python解析apiql查询:

importapiql.parserasparserfromapiql.criteriaimportCriteria,Conjunction,Disjunction,Predicate# ...parsed_criteria=parser.parse('and(title like "Monty";genres == null;ignored!=false;release_year<=1975)')syntax_tree=Criteria([Conjunction([Predicate('title','like','Monty'),Predicate('genres','==',None),Predicate('ignored','!=',False),Predicate('release_year','<=',1975)])])# parsed_criteria is equal to syntax_treeassertEqual(syntax_tree,parsed_criteria)

查询之旅

现在我们可以使用apiql来过滤资源。由于box apiql提供sqlalchemy orm集成(但是可以与其他后端集成)。本节展示了所有基本查询示例。完整的查询功能列表可以在测试套件中找到。

所有示例都将使用此示例数据模型:

Base=declarative_base()classGenre(Base):__tablename__='genre'id=Column(Integer,primary_key=True)name=Column(String)genre_id=Column(Integer)movie_id=Column(Integer,ForeignKey('movie.id'),nullable=True)classMovie(Base):__tablename__='movie'id=Column(Integer,primary_key=True)title=Column(String)original_title=Column(String)release_year=Column(Integer)source=Column(String)rating=Column(String)created_datetime=Column(DateTime,default=datetime.utcnow)genres=relationship('Genre',cascade="all",backref="movie",lazy=True)drama=Genre(name="Drama",genre_id=1)scifi=Genre(name="Sci-Fi",genre_id=2)war=Genre(name="War",genre_id=3)adventure=Genre(name="Adventure",genre_id=4)comedy=Genre(name="Comedy",genre_id=5)monty_python=Movie(title="Monty Python and the Holy Grail",release_year=1975,source="IMDB",rating="8",genres=[comedy,adventure])jurassic_park=Movie(title="Jurassic Park",release_year=1993,source='IMDB',rating="9",genres=[adventure,scifi])apocalypse_now=Movie(title="Apocalypse Now",release_year=1979,source="TMDB",rating="9",original_title="Apocalypse Now, The",genres=[drama,war])gosford_park=Movie(title="Gosford Park",release_year=2001,source='IMDB',rating="7",genres=[drama])session.add(monty_python)session.add(jurassic_park)session.add(apocalypse_now)session.add(gosford_park)

sqlalchemy集成的一个主要入口点是with_criteria扩展方法,它使用apiql功能扩展普通的sqlalchemy查询对象。with_criteria遵循基本的sqlalchemy约定,因此它可以与nativefilter_byfilter函数一起自由使用。

以下示例显示APIQL查询及其本机SQLalchemy表示。

简单连接条件
fromapiql.backends.sqlalchemy.ormimportwith_criteriaactual=session.query(Movie).with_criteria('and(rating=="8";release_year==1975)')# is equivalent toexpected=session.query(Movie).filter(and_(Movie.rating==8,Movie.release_year==1975))

简单析取准则
fromapiql.backends.sqlalchemy.ormimportwith_criteriaactual=session.query(Movie).with_criteria('or(rating=="8";release_year==1993;source=="TMDB")')# is equivalent toexpected=session.query(Movie).filter(or_(Movie.rating==8,Movie.release_year==1993,Movie.source=='TMDB'))

<;>;谓词
actual=session.query(Movie).with_criteria('and(release_year > 1975; release_year < 2001)')# is equivalent toexpected=session.query(Movie).filter(and_(Movie.release_year>1975,Movie.release_year<2001))

谓词

actual=session.query(Movie).with_criteria('or(title like "THE"; original_title ilike "THE")')# is equivalent toexpected=session.query(Movie).filter(or_(Movie.title.like('%THE%'),Movie.original_title.ilike("%THE%")))

不喜欢谓词

actual=session.query(Movie).with_criteria('and(title notlike "the"; release_year > 1990)')# is equivalent toexpected=session.query(Movie).filter(and_(Movie.title.notlike('%the%'),Movie.release_year>1990))

innotin谓词
actual=session.query(Movie).with_criteria('release_year in (1979, 2001))')# is equivalent toexpected=session.query(Movie).filter(Movie.release_year.in_((1979,2001)))

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
0

为空不为空谓词

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
1

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
2

使用结束谓词启动

以下查询是等价的

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
3

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
4

包含谓词

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
5

日期时间文本

以下查询是等价的

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
6

连接和别名实体

连接也受支持。以下查询是等价的:

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
7

apiql支持别名实体:

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
8

使用烧瓶sqlalchemy完成API示例

apiql在任何可能的情况下都与技术无关,可以与所有流行的python web框架(flask、bottle、django等)一起使用。

(附带说明:apiql引用实现也完全支持flask sqlalchemy扩展)。

这是一个简单的flask api,带有apiql过滤(假设moviegenre类是jsonserializable)

[{"title":"Monty Python and the Holy Grail","release_year":1975,"original_title":"Monty Python and the Holy Grail""created_datetime":"2019-05-01T09:17:06.527181+00:00","external_id":"762","genres":[{"id":1,"name":"Comedy"},{"id":2,"name":"Adventure"},{"id":2,"name":"Fantasy"}],"id":1,"plot":"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]","rating":6.0,"source":"tmdb","ignored":false},...]
9

现在我们可以使用apiql筛选资源:

wget-q-o-'localhost:5000/api/电影?filter=和(或(标题类似"python";原始标题类似"python");source=="tmdb")'

注意:当API使用者未指定筛选查询时,我们可以使用空字符串。

此查询:

wget-q-o-"本地主机:5000/api/movies"

将返回所有未筛选的资源。

白名单api属性

在大多数情况下,api提供者只提供对消费者可以使用的属性的有限访问。apiql通过在查询中访问哪些属性来支持此功能(无论资源中公开了哪些属性,这都不会改变)。

可以使用白名单查询扩展方法启用白名单。默认情况下,所有资源属性都是白名单

仅白名单特定属性

apiqljustbuilder只会白名单显式指定的资源属性。

importapiql.parserasparserfromapiql.criteriaimportCriteria,Conjunction,Disjunction,Predicate# ...parsed_criteria=parser.parse('and(title like "Monty";genres == null;ignored!=false;release_year<=1975)')syntax_tree=Criteria([Conjunction([Predicate('title','like','Monty'),Predicate('genres','==',None),Predicate('ignored','!=',False),Predicate('release_year','<=',1975)])])# parsed_criteria is equal to syntax_treeassertEqual(syntax_tree,parsed_criteria)
0

现在,这个查询:

wget-q-o-'http://localhost:5000/api/movies?过滤器=发布年份==2001'

将工作正常,因为movie.release_year已白名单,

但是,此呼叫:

wget-q-o-'http://localhost:5000/api/movies?过滤器=额定值="8.0"'

将失败:

valueerror:无效的查询属性:"rating"

白名单所有属性

所有内容生成器白名单所有实体(或实体)属性。这是未指定白名单时的默认行为。apiql引擎将扫描所有查询实体,并列出所有属性。

importapiql.parserasparserfromapiql.criteriaimportCriteria,Conjunction,Disjunction,Predicate# ...parsed_criteria=parser.parse('and(title like "Monty";genres == null;ignored!=false;release_year<=1975)')syntax_tree=Criteria([Conjunction([Predicate('title','like','Monty'),Predicate('genres','==',None),Predicate('ignored','!=',False),Predicate('release_year','<=',1975)])])# parsed_criteria is equal to syntax_treeassertEqual(syntax_tree,parsed_criteria)
1

注意,在这种情况下,只有电影属性被白名单,而所有流派属性都不被白名单。

在这种情况下:

wget-q-o-'http://localhost:5000/API/电影?过滤器=额定值="8.0"'

将按预期工作。但是,此查询:

wget-q-o-'http://localhost:5000/api/movies?filter=name=="科幻"'

将再次失败。

白名单所有属性,指定的属性集除外

之外的所有内容生成器都会列出所有属性,但在but子句中指定的属性除外。

importapiql.parserasparserfromapiql.criteriaimportCriteria,Conjunction,Disjunction,Predicate# ...parsed_criteria=parser.parse('and(title like "Monty";genres == null;ignored!=false;release_year<=1975)')syntax_tree=Criteria([Conjunction([Predicate('title','like','Monty'),Predicate('genres','==',None),Predicate('ignored','!=',False),Predicate('release_year','<=',1975)])])# parsed_criteria is equal to syntax_treeassertEqual(syntax_tree,parsed_criteria)
2

现在,此调用将失败,因为movie.id没有白名单:

wget-q-o-'http://localhost:5000/api/movies?过滤器=id==1'

合并白名单

最后,合并的白名单生成器可以合并(合并)两个白名单。以下示例:

importapiql.parserasparserfromapiql.criteriaimportCriteria,Conjunction,Disjunction,Predicate# ...parsed_criteria=parser.parse('and(title like "Monty";genres == null;ignored!=false;release_year<=1975)')syntax_tree=Criteria([Conjunction([Predicate('title','like','Monty'),Predicate('genres','==',None),Predicate('ignored','!=',False),Predicate('release_year','<=',1975)])])# parsed_criteria is equal to syntax_treeassertEqual(syntax_tree,parsed_criteria)
3

将列出movie中的所有属性,并且只列出genre.namefromgenre

前缀属性

可以在查询属性前面加上前缀,以便更方便用户使用。例如,在上面的示例中,name属性将解析为genre.name(这里moviegenre之间没有列名冲突)。然而,从消费者的角度来看,将这个属性映射到更明显的东西会更优雅;前缀正是这样做的。下面是一个示例:

importapiql.parserasparserfromapiql.criteriaimportCriteria,Conjunction,Disjunction,Predicate# ...parsed_criteria=parser.parse('and(title like "Monty";genres == null;ignored!=false;release_year<=1975)')syntax_tree=Criteria([Conjunction([Predicate('title','like','Monty'),Predicate('genres','==',None),Predicate('ignored','!=',False),Predicate('release_year','<=',1975)])])# parsed_criteria is equal to syntax_treeassertEqual(syntax_tree,parsed_criteria)
4

映射属性

有时我们希望以不同的名称公开查询属性(例如,我们希望保持向后契约的兼容性)。映射的函数is就是这样做的。

假设我们想映射genre.name来查询属性kind,这样我们就可以使用更好的查询,比如kind="war"

importapiql.parserasparserfromapiql.criteriaimportCriteria,Conjunction,Disjunction,Predicate# ...parsed_criteria=parser.parse('and(title like "Monty";genres == null;ignored!=false;release_year<=1975)')syntax_tree=Criteria([Conjunction([Predicate('title','like','Monty'),Predicate('genres','==',None),Predicate('ignored','!=',False),Predicate('release_year','<=',1975)])])# parsed_criteria is equal to syntax_treeassertEqual(syntax_tree,parsed_criteria)
5

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

推荐PyPI第三方库


热门话题
JavaEclipseMars没有保存首选项   java梯度同步失败:原因:启动失败:   java如何从嵌套的JSON获取数据?   java如何判断可观察对象中的任何对象满足一个条件?   java将字符串转换为保持相同值的byte[]数组   java有没有办法绕过AuditingEntityListener为测试设置数据?   从/usr/share/java中解析linux JAR依赖关系   安卓 My java函数抛出nullpointerexception?   java Gradle使用正确版本的依赖项   JBoss和Java6中带注释的WebService中的web服务ClassCastException   java如何修复codename one中的简单逻辑错误?   java如何迭代矩阵的索引?   java如何在JPanel不可见时将其保存为图像?   java HashMap如何在Kotlin中实现MutableMap接口?   javascript如何在单击后加载特定片段?   EclipseJava为纳什均衡获取所有玩家/策略组合   JavaSpring:Web服务REST在JSON上产生双反斜杠   java为什么ServletContext#getRealPath(“/”)返回相对路径?   java当我的游戏应该重新启动时,我应该如何处理重置SurfaceView和线程?