fauna.query.template
1import re as _re 2from typing import Optional, Tuple, Iterator, Match 3 4 5class FaunaTemplate: 6 """A template class that supports variables marked with a ${}-sigil. Its primary purpose 7 is to expose an iterator for the template parts that support composition of FQL queries. 8 9 Implementation adapted from https://github.com/python/cpython/blob/main/Lib/string.py 10 11 :param template: A string template e.g. "${my_var} { name }" 12 :type template: str 13 """ 14 15 _delimiter = '$' 16 _idpattern = r'[_a-zA-Z][_a-zA-Z0-9]*' 17 _flags = _re.VERBOSE 18 19 def __init__(self, template: str): 20 """The initializer""" 21 delim = _re.escape(self._delimiter) 22 pattern = fr""" 23 {delim}(?: 24 (?P<escaped>{delim}) | # Escape sequence of two delimiters 25 {{(?P<braced>{self._idpattern})}} | # delimiter and a braced identifier 26 (?P<invalid>) # Other ill-formed delimiter exprs 27 ) 28 """ 29 self._pattern = _re.compile(pattern, self._flags) 30 self._template = template 31 32 def iter(self) -> Iterator[Tuple[Optional[str], Optional[str]]]: 33 """A method that returns an iterator over tuples representing template parts. The 34 first value of the tuple, if not None, is a template literal. The second value of 35 the tuple, if not None, is a template variable. If both are not None, then the 36 template literal comes *before* the variable. 37 38 :raises ValueError: If there is an invalid template placeholder 39 40 :return: An iterator of template parts 41 :rtype: collections.Iterable[Tuple[Optional[str], Optional[str]]] 42 """ 43 match_objects = self._pattern.finditer(self._template) 44 cur_pos = 0 45 for mo in match_objects: 46 if mo.group("invalid") is not None: 47 self._handle_invalid(mo) 48 49 span_start_pos = mo.span()[0] 50 span_end_pos = mo.span()[1] 51 escaped_part = mo.group("escaped") or "" 52 variable_part = mo.group("braced") 53 literal_part: Optional[str] = None 54 55 if cur_pos != span_start_pos: 56 literal_part = \ 57 self._template[cur_pos:span_start_pos] \ 58 + escaped_part 59 60 cur_pos = span_end_pos 61 62 yield literal_part, variable_part 63 64 if cur_pos != len(self._template): 65 yield self._template[cur_pos:], None 66 67 def _handle_invalid(self, mo: Match) -> None: 68 i = mo.start("invalid") 69 lines = self._template[:i].splitlines(keepends=True) 70 71 if not lines: 72 colno = 1 73 lineno = 1 74 else: 75 colno = i - len(''.join(lines[:-1])) 76 lineno = len(lines) 77 78 raise ValueError( 79 f"Invalid placeholder in template: line {lineno}, col {colno}")
class
FaunaTemplate:
6class FaunaTemplate: 7 """A template class that supports variables marked with a ${}-sigil. Its primary purpose 8 is to expose an iterator for the template parts that support composition of FQL queries. 9 10 Implementation adapted from https://github.com/python/cpython/blob/main/Lib/string.py 11 12 :param template: A string template e.g. "${my_var} { name }" 13 :type template: str 14 """ 15 16 _delimiter = '$' 17 _idpattern = r'[_a-zA-Z][_a-zA-Z0-9]*' 18 _flags = _re.VERBOSE 19 20 def __init__(self, template: str): 21 """The initializer""" 22 delim = _re.escape(self._delimiter) 23 pattern = fr""" 24 {delim}(?: 25 (?P<escaped>{delim}) | # Escape sequence of two delimiters 26 {{(?P<braced>{self._idpattern})}} | # delimiter and a braced identifier 27 (?P<invalid>) # Other ill-formed delimiter exprs 28 ) 29 """ 30 self._pattern = _re.compile(pattern, self._flags) 31 self._template = template 32 33 def iter(self) -> Iterator[Tuple[Optional[str], Optional[str]]]: 34 """A method that returns an iterator over tuples representing template parts. The 35 first value of the tuple, if not None, is a template literal. The second value of 36 the tuple, if not None, is a template variable. If both are not None, then the 37 template literal comes *before* the variable. 38 39 :raises ValueError: If there is an invalid template placeholder 40 41 :return: An iterator of template parts 42 :rtype: collections.Iterable[Tuple[Optional[str], Optional[str]]] 43 """ 44 match_objects = self._pattern.finditer(self._template) 45 cur_pos = 0 46 for mo in match_objects: 47 if mo.group("invalid") is not None: 48 self._handle_invalid(mo) 49 50 span_start_pos = mo.span()[0] 51 span_end_pos = mo.span()[1] 52 escaped_part = mo.group("escaped") or "" 53 variable_part = mo.group("braced") 54 literal_part: Optional[str] = None 55 56 if cur_pos != span_start_pos: 57 literal_part = \ 58 self._template[cur_pos:span_start_pos] \ 59 + escaped_part 60 61 cur_pos = span_end_pos 62 63 yield literal_part, variable_part 64 65 if cur_pos != len(self._template): 66 yield self._template[cur_pos:], None 67 68 def _handle_invalid(self, mo: Match) -> None: 69 i = mo.start("invalid") 70 lines = self._template[:i].splitlines(keepends=True) 71 72 if not lines: 73 colno = 1 74 lineno = 1 75 else: 76 colno = i - len(''.join(lines[:-1])) 77 lineno = len(lines) 78 79 raise ValueError( 80 f"Invalid placeholder in template: line {lineno}, col {colno}")
A template class that supports variables marked with a ${}-sigil. Its primary purpose is to expose an iterator for the template parts that support composition of FQL queries.
Implementation adapted from https://github.com/python/cpython/blob/main/Lib/string.py
Parameters
- template: A string template e.g. "${my_var} { name }"
FaunaTemplate(template: str)
20 def __init__(self, template: str): 21 """The initializer""" 22 delim = _re.escape(self._delimiter) 23 pattern = fr""" 24 {delim}(?: 25 (?P<escaped>{delim}) | # Escape sequence of two delimiters 26 {{(?P<braced>{self._idpattern})}} | # delimiter and a braced identifier 27 (?P<invalid>) # Other ill-formed delimiter exprs 28 ) 29 """ 30 self._pattern = _re.compile(pattern, self._flags) 31 self._template = template
The initializer
def
iter(self) -> Iterator[Tuple[Optional[str], Optional[str]]]:
33 def iter(self) -> Iterator[Tuple[Optional[str], Optional[str]]]: 34 """A method that returns an iterator over tuples representing template parts. The 35 first value of the tuple, if not None, is a template literal. The second value of 36 the tuple, if not None, is a template variable. If both are not None, then the 37 template literal comes *before* the variable. 38 39 :raises ValueError: If there is an invalid template placeholder 40 41 :return: An iterator of template parts 42 :rtype: collections.Iterable[Tuple[Optional[str], Optional[str]]] 43 """ 44 match_objects = self._pattern.finditer(self._template) 45 cur_pos = 0 46 for mo in match_objects: 47 if mo.group("invalid") is not None: 48 self._handle_invalid(mo) 49 50 span_start_pos = mo.span()[0] 51 span_end_pos = mo.span()[1] 52 escaped_part = mo.group("escaped") or "" 53 variable_part = mo.group("braced") 54 literal_part: Optional[str] = None 55 56 if cur_pos != span_start_pos: 57 literal_part = \ 58 self._template[cur_pos:span_start_pos] \ 59 + escaped_part 60 61 cur_pos = span_end_pos 62 63 yield literal_part, variable_part 64 65 if cur_pos != len(self._template): 66 yield self._template[cur_pos:], None
A method that returns an iterator over tuples representing template parts. The first value of the tuple, if not None, is a template literal. The second value of the tuple, if not None, is a template variable. If both are not None, then the template literal comes before the variable.
Raises
- ValueError: If there is an invalid template placeholder
Returns
An iterator of template parts