在python上实现查询语言

Shm*_*ikA 5 python json abstract-syntax-tree python-3.x

我正在寻找一种方法来向其他开发人员和可选的客户公开我工作场所的过滤功能。

问题

我想基于向我的其他开发人员以及后来向我们的客户公开的用户定义过滤器,对我的数据(python dicts)实现一种简单的查询语言。

  • 语言应该足够简单以供非开发人员使用
  • 足够安全以避免在我的服务器上远程执行代码
  • 表达能力足以查询如下示例中的数据

在我的 dict / json 数据上公开 SQL 接口会很棒(我不想设置服务器)

示例场景

db = [
  {'first': 'john', 'last': 'doe', 'likes': ['cookies', 'http']},
  {'first': 'jane', 'last': 'doe', 'likes': ['cookies', 'donuts']},
  {'first': 'danny', 'last': 'foo', 'likes': ['http', 'donuts']},
]

query = '(first == "john" or last == "doe") and likes contains "cookies"'
results = run_query(db, query)
Run Code Online (Sandbox Code Playgroud)

这应该返回(在结果中):

[
  {'first': 'john', 'last': 'doe', 'likes': ['cookies', 'http']},
  {'first': 'jane', 'last': 'doe', 'likes': ['cookies', 'donuts']},
]
Run Code Online (Sandbox Code Playgroud)

注意:我不介意更改运算符名称,例如or -> OR contains -> inside或其他任何名称,只要它是人类可读的并且保持语言的相同表达能力

我试过的解决方案

数字用户线

我查看了一些像PLY这样的 DSL 库,但在我看来它们太复杂了,并且涉及一些完成任务的魔法(不确定从哪里开始以及是否值得)

插件

没有找到任何插件系统来为我的用户公开沙盒功能(即更安全的评估)

JSON 查询包

我查看了TinyDB和其他通过 json 实现某种 SQL 的工具,但找不到可以在没有大量自定义的情况下工作的东西。我还查看了pandasql,它总体上看起来不错,但没有维护的库:(

有一个 lucene 包解析器 - luqum基于PLY但它与我的语法树不同(他们有更多的方法)并且 lib 没有真正维护,(我确实考虑稍微操纵这个 lib 以获得我想要的)

SQLite

使用 SQLiteDB 加载我的所有数据(无论是否在内存中),然后对其运行 SQL 查询。没有测试它,但这应该非常简单,因为将我的整个数据加载到 SQL 中只是为了运行我不想做的数据。

我愿意接受建议,甚至对如何改进上述解决方案以使其有效

Ray*_*ger 4

在使用 PLY 进行基于文本的查询之前,我会从常规 Python 类构建核心语言,如下所示:

class Match:
    def __init__(self, **target):
        [[self.key, self.value]] = target.items()
    def __call__(self, obj):
        return self.key in obj and self.value == obj[self.key]

class Contains:        
    def __init__(self, **target):
        [[self.key, self.value]] = target.items()
    def __call__(self, obj):
        return self.key in obj and self.value in obj[self.key]        

class Or:
    def __init__(self, *predicates):
        self.predicates = predicates
    def __call__(self, record):
        return any(predicate(record) for predicate in self.predicates)

class And:
    def __init__(self, *predicates):
        self.predicates = predicates
    def __call__(self, record):
        return all(predicate(record) for predicate in self.predicates)

def run_query(db, query):
    return filter(query, db)

if __name__ == '__main__':

    db = [
      {'first': 'john', 'last': 'doe', 'likes': ['cookies', 'http']},
      {'first': 'jane', 'last': 'doe', 'likes': ['cookies', 'donuts']},
      {'first': 'danny', 'last': 'foo', 'likes': ['http', 'donuts']},
    ]
    query = And(Or(Match(first='john'), Match(last='doe')), Contains(likes='cookies'))
    for result in run_query(db, query):
        print(result)
Run Code Online (Sandbox Code Playgroud)

这输出:

{'first': 'john', 'last': 'doe', 'likes': ['cookies', 'http']}
{'first': 'jane', 'last': 'doe', 'likes': ['cookies', 'donuts']}
Run Code Online (Sandbox Code Playgroud)