Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions find_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ def test_hyphenated(self):
def test_equal_int(self):
self.compare('a == 1', {'a': 1})

def test_plus_operator(self):
self.compare('a == +1', {'a': 1})

def test_minus(self):
self.compare('a == -1', {'a': -1})

def test_more_than_minus(self):
self.compare('a > -1', {'a': {'$gt': -1}})

def test_less_than_minus(self):
self.compare('a < -1', {'a': {'$lt': -1}})

def test_not_equal_string(self):
self.compare('a != "foo"', {'a': {'$ne': 'foo'}})

Expand Down Expand Up @@ -173,6 +185,10 @@ def test_polygon_and_box(self):
{'$geoWithin':
{'$' + shape: [[1, 2], [3, 4], [5, 6]]}}})

def test_symver(self):
self.compare('version == symver("1.0.0 (0)")', {'version': 1000000000.0})


class PqlSchemaAwareTestCase(BasePqlTestCase):

def compare(self, string, expected):
Expand All @@ -184,6 +200,9 @@ def compare(self, string, expected):
def test_sanity(self):
self.compare('a == 3', {'a': 3})

def test_minus(self):
self.compare('a == -1', {'a': -1})

def test_invalid_field(self):
with self.assertRaises(pql.ParseError) as context:
self.compare('b == 3', None)
Expand Down
48 changes: 44 additions & 4 deletions pql/matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
import bson
import datetime
import dateutil.parser
import re
from calendar import timegm


def parse_date(node):
if hasattr(node, 'n'): # it's a number!
if type(node.value) in (int, float): # it's a number!
return datetime.datetime.fromtimestamp(node.n)
try:
return dateutil.parser.parse(node.s)
return dateutil.parser.parse(node.value)
except Exception as e:
raise ParseError('Error parsing date: ' + str(e), col_offset=node.col_offset)

Expand Down Expand Up @@ -111,6 +112,8 @@ def __init__(self, *a, **k):
super(SchemaAwareParser, self).__init__(SchemaAwareOperatorMap(*a, **k))

class FieldName(AstHandler):
def handle_Constant(self, node):
return node.value
def handle_Str(self, node):
return node.s
def handle_Name(self, name):
Expand Down Expand Up @@ -284,8 +287,12 @@ def handle_geoIntersects(self, node):
def handle_geoWithin(self, node):
return {'$geoWithin': GeoShapeParser().handle(self.get_arg(node, 0))}

class SymverFunc(Func):
def handle_symver(self, node):
return self.parse_arg(node, 0, SymverField())

class GenericFunc(StringFunc, IntFunc, ListFunc, DateTimeFunc,
IdFunc, EpochFunc, EpochUTCFunc, GeoFunc):
IdFunc, EpochFunc, EpochUTCFunc, GeoFunc, SymverFunc):
pass

#---Operators---#
Expand Down Expand Up @@ -338,7 +345,7 @@ class Field(AstHandler):
SPECIAL_VALUES = {'None': None,
'null': None}

def handle_NameConstant(self,node):
def handle_Constant(self, node):
try:
return self.SPECIAL_VALUES[str(node.value)]
except KeyError:
Expand All @@ -365,10 +372,21 @@ def handle_Call(self, node):
return StringFunc().handle(node)
def handle_Str(self, node):
return node.s
def handle_Constant(self, node):
return node.value

class IntField(AlgebricField):
def handle_Constant(self, node):
return node.value
def handle_Num(self, node):
return node.n
def handle_UnaryOp(self, node):
op_type = type(node.op)
if (op_type == ast.USub):
return - node.operand.value
else:
return node.operand.value

def handle_Call(self, node):
return IntFunc().handle(node)

Expand All @@ -395,6 +413,8 @@ def handle_Dict(self, node):
for key, value in zip(node.keys, node.values))

class DateTimeField(AlgebricField):
def handle_Constant(self, node):
return parse_date(node.value)
def handle_Str(self, node):
return parse_date(node)
def handle_Num(self, node):
Expand All @@ -403,6 +423,8 @@ def handle_Call(self, node):
return DateTimeFunc().handle(node)

class EpochField(AlgebricField):
def handle_Constant(self, node):
return float(parse_date(node).strftime('%s.%f'))
def handle_Str(self, node):
return float(parse_date(node).strftime('%s.%f'))
def handle_Num(self, node):
Expand All @@ -411,6 +433,8 @@ def handle_Call(self, node):
return EpochFunc().handle(node)

class EpochUTCField(AlgebricField):
def handle_Constant(self, node):
return timegm(parse_date(node).timetuple())
def handle_Str(self, node):
return timegm(parse_date(node).timetuple())
def handle_Num(self, node):
Expand All @@ -419,11 +443,27 @@ def handle_Call(self, node):
return EpochUTCFunc().handle(node)

class IdField(AlgebricField):
def handle_Constant(self, node):
return bson.ObjectId(node.value)
def handle_Str(self, node):
return bson.ObjectId(node.s)
def handle_Call(self, node):
return IdFunc().handle(node)

class SymverField(AlgebricField):
re_version = r'(\d+)\.(\d+)\.(\d+)\s*\((\d+)\)'

def handle_Constant(self, node):
d = re.findall(self.re_version, node.value)[0]
return (int(d[0]) * 1e9) + (int(d[1]) * 1e6) + (int(d[2]) * 1e3) + int(d[3])

def handle_Num(self, node):
d = re.findall(self.re_version, node.value)[0]
return (int(d[0]) * 1e9) + (int(d[1]) * 1e6) + (int(d[2]) * 1e3) + int(d[3])

def handle_Call(self, node):
return SymverFunc().handle(node)

class GenericField(IntField, BoolField, StringField, ListField, DictField, GeoField):
def handle_Call(self, node):
return GenericFunc().handle(node)
7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup

__version__ = '0.5.0'
__version__ = '0.5.1'

setup(name='pql',
version=__version__,
Expand All @@ -12,11 +12,14 @@
"Intended Audience :: Developers",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS :: MacOS X"],
license='BSD',
# I know it's bad practice to not specify a pymongo version, but we only
# require the bson.ObjectId type, It's safe to assume it won't change (famous last words)
install_requires=['pymongo',
'python-dateutil'],
packages=['pql'])
packages=['pql'])
6 changes: 4 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[tox]
envlist = py33,py35
envlist = py{py3,27,35,36,37,38}
skip_missing_interpreters = true

[testenv]
deps=nose
commands=nosetests
commands=nosetests