Welcome to pycql’s documentation!¶
Introduction¶
pycql is a pure python parser of the Common Query Language (CQL) defined in the OGC Catalogue specification.
The basic bare-bone functionality is to parse the given CQL to an abstract syntax tree (AST) representation. This AST can then be used to create filters for databases or search engines.
pycql license¶
Copyright (C) 2019 EOX IT Services GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies of this Software or works derived from this Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Installation¶
pycql can be installed using pip
from the Python Package Index (PyPI):
pip install pycql
You can also install pycql from source:
cd path/to/pycql/
python setup.py install
Usage¶
The basic functionality parses the input string to an abstract syntax tree (AST) representation. This AST can then be used to build database filters or similar functionality.
>>> import pycql
>>> ast = pycql.parse(filter_expression)
What is returned by the pycql.parser.parse()
is the root
pycql.ast.Node
of the AST representation.
Inspection¶
The easiest way to inspect the resulting AST is to use the
pycql.ast.get_repr()
function, which returns a nice string
representation of what was parsed:
>>> 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)')
)
Evaluation¶
In order to create useful filters from the resulting AST, it has to be evaluated. For the Django integration, this was done using a recursive descent into the AST, evaluating the subnodes first and constructing a Q object. Consider having a filters API (for an example look at the Django one) which creates the filter. Now the evaluator looks something like this:
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
As mentionend, the to_filter method is the recursion.
Django integration¶
For Django there is a default bridging implementation, where all the filters
are translated to the Django ORM. In order to use this integration, we need
two dictionaries, one mapping the available fields to the Django model fields,
and one to map the fields that use choices
. Consider the following example
models:
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)
Now we can specify the field mappings and mapping choices to be used when applying the filters:
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),
}
Finally we are able to connect the CQL AST to the Django database models. We also provide factory functions to parse the timestamps, durations, geometries and envelopes, so that they can be used with the ORM layer:
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)
API Documentation¶
pycql¶
pycql.ast¶
-
class
pycql.ast.
ArithmeticExpressionNode
(lhs, rhs, op)¶ Node class to represent arithmetic operation expressions with two sub-expressions and an operator.
Variables: - lhs – the left hand side node of this arithmetic expression
- rhs – the right hand side node of this arithmetic expression
- op – the comparison type. One of
"+"
,"-"
,"*"
,"/"
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
AttributeExpression
(name)¶ Node class to represent attribute lookup expressions
Variables: name – the name of the attribute to be accessed
-
class
pycql.ast.
BBoxPredicateNode
(lhs, minx, miny, maxx, maxy, crs=None)¶ Node class to represent a bounding box predicate.
Variables: - lhs – the left hand side node of this predicate
- minx – the minimum X value of the bounding box
- miny – the minimum Y value of the bounding box
- maxx – the maximum X value of the bounding box
- maxx – the maximum Y value of the bounding box
- crs – the coordinate reference system identifier for the CRS the BBox is expressed in
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
BetweenPredicateNode
(lhs, low, high, not_)¶ Node class to represent a BETWEEN predicate: to check whether an expression value within a range.
Variables: - lhs – the left hand side node of this comparison
- low – the lower bound of the clause
- high – the upper bound of the clause
- not – whether the predicate shall be negated
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
CombinationConditionNode
(lhs, rhs, op)¶ Node class to represent a condition to combine two other conditions using either AND or OR.
Variables: - lhs – the left hand side node of this combination
- rhs – the right hand side node of this combination
- op – the combination type. Either
"AND"
or"OR"
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
ComparisonPredicateNode
(lhs, rhs, op)¶ Node class to represent a comparison predicate: to compare two expressions using a comparison operation.
Variables: - lhs – the left hand side node of this comparison
- rhs – the right hand side node of this comparison
- op – the comparison type. One of
"="
,"<>"
,"<"
,">"
,"<="
,">="
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
ConditionNode
¶ The base class for all nodes representing a condition
-
class
pycql.ast.
ExpressionNode
¶ The base class for all nodes representing expressions
-
class
pycql.ast.
InPredicateNode
(lhs, sub_nodes, not_)¶ Node class to represent list checking predicate.
Variables: - lhs – the left hand side node of this predicate
- sub_nodes – the list of sub nodes to check the inclusion against
- not – whether the predicate shall be negated
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
LikePredicateNode
(lhs, rhs, case, not_)¶ Node class to represent a wildcard sting matching predicate.
Variables: - lhs – the left hand side node of this predicate
- rhs – the right hand side node of this predicate
- case – whether the comparison shall be case sensitive
- not – whether the predicate shall be negated
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
LiteralExpression
(value)¶ Node class to represent literal value expressions
Variables: value – the value of the literal
-
class
pycql.ast.
Node
¶ The base class for all other nodes to display the AST of CQL.
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
-
class
pycql.ast.
NotConditionNode
(sub_node)¶ Node class to represent a negation condition.
Variables: sub_node – the condition node to be negated -
get_sub_nodes
()¶ Returns the sub-node for the negated condition.
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
-
class
pycql.ast.
NullPredicateNode
(lhs, not_)¶ Node class to represent null check predicate.
Variables: - lhs – the left hand side node of this predicate
- not – whether the predicate shall be negated
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
PredicateNode
¶ The base class for all nodes representing a predicate
-
class
pycql.ast.
SpatialPredicateNode
(lhs, rhs, op, pattern=None, distance=None, units=None)¶ Node class to represent spatial relation predicate.
Variables: - lhs – the left hand side node of this comparison
- rhs – the right hand side node of this comparison
- op – the comparison type. One of
"INTERSECTS"
,"DISJOINT"
,"CONTAINS"
,"WITHIN"
,"TOUCHES"
,"CROSSES"
,"OVERLAPS"
,"EQUALS"
,"RELATE"
,"DWITHIN"
,"BEYOND"
- pattern – the relationship patter for the
"RELATE"
operation - distance – the distance for distance related operations
- units – the units for distance related operations
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
class
pycql.ast.
TemporalPredicateNode
(lhs, rhs, op)¶ Node class to represent temporal predicate.
Variables: - lhs – the left hand side node of this comparison
- rhs – the right hand side node of this comparison
- op – the comparison type. One of
"BEFORE"
,"BEFORE OR DURING"
,"DURING"
,"DURING OR AFTER"
,"AFTER"
-
get_sub_nodes
()¶ Get a list of sub-node of this node.
Returns: a list of all sub-nodes Return type: list[Node]
-
get_template
()¶ Get a template string (using the
%
operator) to represent the current node and sub-nodes. The template string must provide a template replacement for each sub-node reported byget_sub_nodes()
.Returns: the template to render
-
pycql.ast.
get_repr
(node, indent_amount=0, indent_incr=4)¶ Get a debug representation of the given AST node.
indent_amount
andindent_incr
are for the recursive call and don’t need to be passed.Parameters: Returns: the represenation of the node
Return type:
pycql.lexer¶
pycql.parser¶
-
pycql.parser.
parse
(cql, geometry_factory=<class 'pycql.values.Geometry'>, bbox_factory=<class 'pycql.values.BBox'>, time_factory=<class 'pycql.values.Time'>, duration_factory=<class 'pycql.values.Duration'>)¶ Parses the passed CQL to its AST interpretation.
Parameters: - cql (str) – the CQL expression string to parse
- geometry_factory – the geometry parsing function: it shall parse the given WKT geometry string the relevant type
- bbox_factory – the bbox parsing function: it shall parse the given BBox tuple the relevant type.
- time_factory – the timestamp parsing function: it shall parse the given ISO8601 timestamp string tuple the relevant type.
- duration_factory – the duration parsing function: it shall parse the given ISO8601 furation string tuple the relevant type.
Returns: the parsed CQL expression as an AST
Return type:
pycql.util¶
-
pycql.util.
parse_duration
(value)¶ Parses an ISO 8601 duration string into a python timedelta object. Raises a
ValueError
if a conversion was not possible.Parameters: value (str) – the ISO8601 duration string to parse Returns: the parsed duration Return type: datetime.timedelta