动态解析和构建TinyDB查询

问题描述:

是否可以在TinyDB中动态构建查询?它的逻辑查询操作是这样的:动态解析和构建TinyDB查询

>>> from tinydb import TinyDB, where 
>>> db = TinyDB('db.json') 
>>> # Logical AND: 
>>> db.search((where('int') == 1) & (where('char') == 'b')) 
[{'int': 1, 'char': 'b'}] 

但我需要从用户的输入条件动态构建查询。我能想出的唯一办法是连接的条件为一个字符串和exec这样说:

#!/usr/bin/env python3 
import shlex 
from tinydb import TinyDB, where 

# create db sample 
db = TinyDB('test.json') 
db.insert({'id': '1', 'name': 'Tom', 'age': '10', 'grade': '4'}) 
db.insert({'id': '2', 'name': 'Alice', 'age': '9', 'grade': '3'}) 
db.insert({'id': '3', 'name': 'John', 'age': '11', 'grade': '5'}) 
db.close() 

# query test 
db = TinyDB('test.json') 
q = input("query for name/age/grade: ") 
# name='Tom' grade='4' 
qdict = dict(token.split('=') for token in shlex.split(q)) 

result = [] 
query = "result = db.search(" 
qlen = len(qdict) 
count = 0 
for key, value in qdict.items(): 
    query += "(where('%s') == '%s')" % (key, value) 
    count += 1 
    if count < qlen: 
     query += " & " 

query += ')' 
exec(query) 
print(result) 
# [{'age': '10', 'id': '1', 'grade': '4', 'name': 'Tom'}] 

是否还有更好的和优雅的方式来做到这一点?非常感谢。

这里是一个最小解决方案,支持以下运算符:

==!=>=<-><

查询的语法是:

<key> <operator> <value> 

必须将每个令牌分隔一个温泉CE。

代码:

#!/usr/bin/env python3 


from __future__ import print_function 


try: 
    import readline # noqa 
except ImportError: 
    print("Warning: No readline support available!") 


try: 
    input = raw_input 
except NameError: 
    pass 


import sys 
from os import path 
from operator import eq, ge, gt, le, lt, ne 


from tinydb import TinyDB, where 


ops = { 
    "==": eq, 
    "!=": ne, 
    "<=": le, 
    ">=": ge, 
    "<": lt, 
    ">": gt, 
} 


def isint(s): 
    return all(map(str.isdigit, s)) 


def isfloat(s): 
    return "." in s and isint(s.replace(".", "")) 


def createdb(filename): 
    db = TinyDB(filename) 
    db.insert({"id": 1, "name": "Tom", "age": 10, "grade": 4}) 
    db.insert({"id": 2, "name": "Alice", "age": 9, "grade": 3}) 
    db.insert({"id": 3, "name": "John", "age": 11, "grade": 5}) 
    db.close() 


def opendb(filename): 
    return TinyDB(filename) 


def parse_query(s): 
    qs = [] 

    tokens = s.split("&") 
    tokens = map(str.strip, tokens) 

    for token in tokens: 
     try: 
      k, op, v = token.split(" ", 3) 
     except Exception as e: 
      print("Syntax Error with {0:s}: {1:s}".format(repr(s), e)) 
      return where(None) 

     opf = ops.get(op, None) 
     if opf is None: 
      print("Unknown operator: {0:s}".format(op)) 
      return where(None) 

     if isfloat(v): 
      v = float(v) 
     elif isint(v): 
      v = int(v) 

     qs.append(opf(where(k), v)) 

    return reduce(lambda a, b: a & b, qs) 


def main(): 
    if not path.exists(sys.argv[1]): 
     createdb(sys.argv[1]) 

    db = opendb(sys.argv[1]) 

    while True: 
     try: 
      s = input("Query: ") 
      q = parse_query(s) 
      print(repr(db.search(q))) 
     except (EOFError, KeyboardInterrupt): 
      break 

    db.close() 


if __name__ == "__main__": 
    main() 

演示:

$ python foo.py test.json 
Query: name == Tom 
[{u'grade': 4, u'age': 10, u'id': 1, u'name': u'Tom'}] 
Query: grade >= 3 
[{u'grade': 4, u'age': 10, u'id': 1, u'name': u'Tom'}, {u'grade': 3, u'age': 9, u'id': 2, u'name': u'Alice'}, {u'grade': 5, u'age': 11, u'id': 3, u'name': u'John'}] 
Query: grade == 3 
[{u'grade': 3, u'age': 9, u'id': 2, u'name': u'Alice'}] 
Query: age <= 13 
[{u'grade': 4, u'age': 10, u'id': 1, u'name': u'Tom'}, {u'grade': 3, u'age': 9, u'id': 2, u'name': u'Alice'}, {u'grade': 5, u'age': 11, u'id': 3, u'name': u'John'}] 
Query: 

注:

  • 我只测试了这个关于Python 2.7
  • 我用最近tinydb
  • 我改变你的“测试数据”,包括“真实”数据类型

但最重要的;这以任何方式使用eval()exec尝试解析输入并建立查询对象。

+0

我真的很感激你的精心编码。看起来核心的魔法就是使用'操作员'模块,我之前并不知道这个模块。顺便说一句,Python 3已将'reduce()'移动到'functools'模块中,Guido在[https://docs.python.org/3.0/whatsnew/3.0.html](Python 3.0中的新增功能)中说:“Removed' reduce()'。如果你确实需要它,可以使用'functools.reduce();但是,99%的时间显式for循环更具可读性。“我想你的代码占了1%的时间。 :-) – AlvaPan

+2

我很高兴你觉得这有用;我希望别人也会! –