File size: 4,615 Bytes
f9dacfc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
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}
|