pycql是OGC CQL标准的纯Python解析器实现
项目描述
pycql
pycql是OGC CQL标准的纯Python解析器实现
安装
pip install pycql
使用
基本功能将输入字符串解析为抽象语法树(AST)表示。然后可以使用该AST构建数据库过滤器或类似功能。
>>> import pycql
>>> ast = pycql.parse(filter_expression)
检查
检查生成的AST的最简单方法是使用get_repr
函数,它返回解析结果的漂亮字符串表示形式
>>> ast = pycql.parse('id = 10')
>>> print(pycql.get_repr(ast))
ATTRIBUTE id = LITERAL 10.0
>>>
>>>
>>> filter_expr = '(number BETWEEN 5 AND 10 AND string NOT LIKE "%B") OR INTERSECTS(geometry, LINESTRING(0 0, 1 1))'
>>> print(pycql.get_repr(pycql.parse(filter_expr)))
(
(
ATTRIBUTE number BETWEEN LITERAL 5.0 AND LITERAL 10.0
) AND (
ATTRIBUTE string NOT ILIKE LITERAL '%B'
)
) OR (
INTERSECTS(ATTRIBUTE geometry, LITERAL GEOMETRY 'LINESTRING(0 0, 1 1)')
)
评估
为了从生成的AST创建有用的过滤器,必须对其进行评估。对于Django集成,这是通过递归下降到AST,首先评估子节点并构建一个Q
对象来完成的。考虑有一个创建过滤器的filters
API(例如,可以查看Django的一个例子),现在评估器看起来像这样
from pycql.ast import *
from myapi import filters # <- this is where the filters are created.
# of course, this can also be done in the
# evaluator itself
class FilterEvaluator:
def __init__(self, field_mapping=None, mapping_choices=None):
self.field_mapping = field_mapping
self.mapping_choices = mapping_choices
def to_filter(self, node):
to_filter = self.to_filter
if isinstance(node, NotConditionNode):
return filters.negate(to_filter(node.sub_node))
elif isinstance(node, CombinationConditionNode):
return filters.combine(
(to_filter(node.lhs), to_filter(node.rhs)), node.op
)
elif isinstance(node, ComparisonPredicateNode):
return filters.compare(
to_filter(node.lhs), to_filter(node.rhs), node.op,
self.mapping_choices
)
elif isinstance(node, BetweenPredicateNode):
return filters.between(
to_filter(node.lhs), to_filter(node.low),
to_filter(node.high), node.not_
)
elif isinstance(node, BetweenPredicateNode):
return filters.between(
to_filter(node.lhs), to_filter(node.low),
to_filter(node.high), node.not_
)
# ... Some nodes are left out for brevity
elif isinstance(node, AttributeExpression):
return filters.attribute(node.name, self.field_mapping)
elif isinstance(node, LiteralExpression):
return node.value
elif isinstance(node, ArithmeticExpressionNode):
return filters.arithmetic(
to_filter(node.lhs), to_filter(node.rhs), node.op
)
return node
如前所述,to_filter
方法是递归的。
测试
可以使用pytest
测试基本功能。
python -m pytest
有一个用于测试Django集成的测试项目/应用程序。这是使用以下命令测试的
python manage.py test testapp
与Django的集成
对于Django,有一个默认的桥接实现,其中所有过滤器都被转换成Django ORM。为了使用此集成,我们需要两个字典,一个将可用的字段映射到Django模型字段,另一个将使用choices
的字段进行映射。考虑以下示例模型
from django.contrib.gis.db import models
optional = dict(null=True, blank=True)
class Record(models.Model):
identifier = models.CharField(max_length=256, unique=True, null=False)
geometry = models.GeometryField()
float_attribute = models.FloatField(**optional)
int_attribute = models.IntegerField(**optional)
str_attribute = models.CharField(max_length=256, **optional)
datetime_attribute = models.DateTimeField(**optional)
choice_attribute = models.PositiveSmallIntegerField(choices=[
(1, 'ASCENDING'),
(2, 'DESCENDING'),],
**optional)
class RecordMeta(models.Model):
record = models.ForeignKey(Record, on_delete=models.CASCADE, related_name='record_metas')
float_meta_attribute = models.FloatField(**optional)
int_meta_attribute = models.IntegerField(**optional)
str_meta_attribute = models.CharField(max_length=256, **optional)
datetime_meta_attribute = models.DateTimeField(**optional)
choice_meta_attribute = models.PositiveSmallIntegerField(choices=[
(1, 'X'),
(2, 'Y'),
(3, 'Z')],
**optional)
现在我们可以指定在应用过滤器时要使用的字段映射和映射选项
FIELD_MAPPING = {
'identifier': 'identifier',
'geometry': 'geometry',
'floatAttribute': 'float_attribute',
'intAttribute': 'int_attribute',
'strAttribute': 'str_attribute',
'datetimeAttribute': 'datetime_attribute',
'choiceAttribute': 'choice_attribute',
# meta fields
'floatMetaAttribute': 'record_metas__float_meta_attribute',
'intMetaAttribute': 'record_metas__int_meta_attribute',
'strMetaAttribute': 'record_metas__str_meta_attribute',
'datetimeMetaAttribute': 'record_metas__datetime_meta_attribute',
'choiceMetaAttribute': 'record_metas__choice_meta_attribute',
}
MAPPING_CHOICES = {
'choiceAttribute': dict(Record._meta.get_field('choice_attribute').choices),
'choiceMetaAttribute': dict(RecordMeta._meta.get_field('choice_meta_attribute').choices),
}
最后,我们能够将CQL AST连接到Django数据库模型。我们还提供了用于解析时间戳、持续时间、几何形状和包围盒的工厂函数,以便它们可以与ORM层一起使用
from pycql.integrations.django import to_filter, parse
cql_expr = 'strMetaAttribute LIKE "%parent%" AND datetimeAttribute BEFORE 2000-01-01T00:00:01Z'
# NOTE: we are using the django integration `parse` wrapper here
ast = parse(cql_expr)
filters = to_filter(ast, mapping, mapping_choices)
qs = Record.objects.filter(**filters)
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。
源分布
pycql-0.0.12.tar.gz (25.8 kB 查看散列)
构建分布
pycql-0.0.12-py2.py3-none-any.whl (33.6 kB 查看散列)