BinaryONe
initial Commit
f9dacfc
import re
from pypeg2 import List, contiguous, csl, name, optional, parse
from .exceptions import QueryFormatError
class IncludedField(List):
grammar = name()
class ExcludedField(List):
grammar = contiguous('-', name())
class AllFields(str):
grammar = '*'
class BaseArgument(List):
@property
def value(self):
return self[0]
class ArgumentWithoutQuotes(BaseArgument):
grammar = name(), ':', re.compile(r'[^,:"\'\)]+')
class ArgumentWithSingleQuotes(BaseArgument):
grammar = name(), ':', "'", re.compile(r'[^\']+'), "'"
class ArgumentWithDoubleQuotes(BaseArgument):
grammar = name(), ':', '"', re.compile(r'[^"]+'), '"'
class Arguments(List):
grammar = optional(csl(
[
ArgumentWithoutQuotes,
ArgumentWithSingleQuotes,
ArgumentWithDoubleQuotes
],
separator=','
))
class ArgumentsBlock(List):
grammar = optional('(', Arguments, ')')
@property
def arguments(self):
if self[0] is None:
return [] # No arguments
return self[0]
class ParentField(List):
"""
According to ParentField grammar:
self[0] returns IncludedField instance,
self[1] returns Block instance
"""
@property
def name(self):
return self[0].name
@property
def block(self):
return self[1]
class BlockBody(List):
grammar = optional(csl(
[ParentField, IncludedField, ExcludedField, AllFields],
separator=','
))
class Block(List):
grammar = ArgumentsBlock, '{', BlockBody, '}'
@property
def arguments(self):
return self[0].arguments
@property
def body(self):
return self[1]
# ParentField grammar,
# We don't include `ExcludedField` here because
# exclude operator(-) on a parent field should
# raise syntax error, e.g {name, -location{city}}
# `IncludedField` is a parent field and `Block`
# contains its sub fields
ParentField.grammar = IncludedField, Block
class Parser(object):
def __init__(self, query):
self._query = query
def get_parsed(self):
parse_tree = parse(self._query, Block)
return self._transform_block(parse_tree)
def _transform_block(self, block):
fields = {
"include": [],
"exclude": [],
"arguments": {}
}
for argument in block.arguments:
argument = {str(argument.name): argument.value}
fields['arguments'].update(argument)
for field in block.body:
# A field may be a parent or included field or excluded field
field = self._transform_field(field)
if isinstance(field, dict):
# A field is a parent
fields["include"].append(field)
elif isinstance(field, IncludedField):
fields["include"].append(str(field.name))
elif isinstance(field, ExcludedField):
fields["exclude"].append(str(field.name))
elif isinstance(field, AllFields):
# include all fields
fields["include"].append("*")
if fields["exclude"]:
# fields['include'] should contain only nested fields
# We should add `*` operator in fields['include']
add_include_all_operator = True
for field in fields["include"]:
if field == "*":
# `*` operator is alredy in fields['include']
add_include_all_operator = False
continue
if isinstance(field, str):
# Including and excluding fields on the same field level
msg = (
"Can not include and exclude fields on the same "
"field level"
)
raise QueryFormatError(msg)
if add_include_all_operator:
# Make sure we include * operator
fields["include"].append("*")
return fields
def _transform_field(self, field):
# A field may be a parent or included field or excluded field
if isinstance(field, ParentField):
return self._transform_parent_field(field)
elif isinstance(field, (IncludedField, ExcludedField, AllFields)):
return field
def _transform_parent_field(self, parent_field):
parent_field_name = str(parent_field.name)
parent_field_value = self._transform_block(parent_field.block)
return {parent_field_name: parent_field_value}