json-e
Advanced tools
| Metadata-Version: 2.1 | ||
| Name: json-e | ||
| Version: 4.5.3 | ||
| Version: 4.6.0 | ||
| Summary: A data-structure parameterization system written for embedding context in JSON objects | ||
@@ -5,0 +5,0 @@ Home-page: https://json-e.js.org |
@@ -8,3 +8,3 @@ from __future__ import absolute_import, print_function, unicode_literals | ||
| _context_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*$') | ||
| _context_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") | ||
@@ -16,5 +16,6 @@ | ||
| if not all(_context_re.match(c) for c in context): | ||
| raise TemplateError('top level keys of context must follow ' | ||
| '/[a-zA-Z_][a-zA-Z0-9_]*/') | ||
| full_context = {'now': fromNow('0 seconds', None)} | ||
| raise TemplateError( | ||
| "top level keys of context must follow " "/[a-zA-Z_][a-zA-Z0-9_]*/" | ||
| ) | ||
| full_context = {"now": fromNow("0 seconds", None)} | ||
| full_context.update(builtins.build()) | ||
@@ -26,3 +27,3 @@ full_context.update(context) | ||
| if containsFunctions(rv): | ||
| raise TemplateError('evaluated template contained uncalled functions') | ||
| raise TemplateError("evaluated template contained uncalled functions") | ||
| return rv |
+49
-34
@@ -14,5 +14,8 @@ from __future__ import absolute_import, print_function, unicode_literals | ||
| def builtin(name, variadic=None, argument_tests=None, minArgs=None, needs_context=False): | ||
| def builtin( | ||
| name, variadic=None, argument_tests=None, minArgs=None, needs_context=False | ||
| ): | ||
| def wrap(fn): | ||
| if variadic: | ||
| def invoke(context, *args): | ||
@@ -22,7 +25,11 @@ if minArgs: | ||
| raise BuiltinError( | ||
| 'invalid arguments to builtin: {}: expected at least {} arguments'.format(name, minArgs) | ||
| "invalid arguments to builtin: {}: expected at least {} arguments".format( | ||
| name, minArgs | ||
| ) | ||
| ) | ||
| for arg in args: | ||
| if not variadic(arg): | ||
| raise BuiltinError('invalid arguments to builtin: {}'.format(name)) | ||
| raise BuiltinError( | ||
| "invalid arguments to builtin: {}".format(name) | ||
| ) | ||
| if needs_context is True: | ||
@@ -33,8 +40,13 @@ return fn(context, *args) | ||
| elif argument_tests: | ||
| def invoke(context, *args): | ||
| if len(args) != len(argument_tests): | ||
| raise BuiltinError('invalid arguments to builtin: {}'.format(name)) | ||
| raise BuiltinError( | ||
| "invalid arguments to builtin: {}".format(name) | ||
| ) | ||
| for t, arg in zip(argument_tests, args): | ||
| if not t(arg): | ||
| raise BuiltinError('invalid arguments to builtin: {}'.format(name)) | ||
| raise BuiltinError( | ||
| "invalid arguments to builtin: {}".format(name) | ||
| ) | ||
| if needs_context is True: | ||
@@ -45,2 +57,3 @@ return fn(context, *args) | ||
| else: | ||
| def invoke(context, *args): | ||
@@ -76,44 +89,46 @@ if needs_context is True: | ||
| def anything(v): | ||
| return isinstance(v, (string, int, float, list, dict)) or v is None or callable(v) | ||
| return ( | ||
| isinstance(v, (string, int, float, list, dict)) or v is None or callable(v) | ||
| ) | ||
| # --- | ||
| builtin('min', variadic=is_number, minArgs=1)(min) | ||
| builtin('max', variadic=is_number, minArgs=1)(max) | ||
| builtin('sqrt', argument_tests=[is_number])(math.sqrt) | ||
| builtin('abs', argument_tests=[is_number])(abs) | ||
| builtin("min", variadic=is_number, minArgs=1)(min) | ||
| builtin("max", variadic=is_number, minArgs=1)(max) | ||
| builtin("sqrt", argument_tests=[is_number])(math.sqrt) | ||
| builtin("abs", argument_tests=[is_number])(abs) | ||
| @builtin('ceil', argument_tests=[is_number]) | ||
| @builtin("ceil", argument_tests=[is_number]) | ||
| def ceil(v): | ||
| return int(math.ceil(v)) | ||
| @builtin('floor', argument_tests=[is_number]) | ||
| @builtin("floor", argument_tests=[is_number]) | ||
| def floor(v): | ||
| return int(math.floor(v)) | ||
| @builtin('lowercase', argument_tests=[is_string]) | ||
| @builtin("lowercase", argument_tests=[is_string]) | ||
| def lowercase(v): | ||
| return v.lower() | ||
| @builtin('uppercase', argument_tests=[is_string]) | ||
| @builtin("uppercase", argument_tests=[is_string]) | ||
| def lowercase(v): | ||
| return v.upper() | ||
| builtin('len', argument_tests=[is_string_or_array])(len) | ||
| builtin('str', argument_tests=[anything_except_array])(to_str) | ||
| builtin('number', variadic=is_string, minArgs=1)(float) | ||
| builtin("len", argument_tests=[is_string_or_array])(len) | ||
| builtin("str", argument_tests=[anything_except_array])(to_str) | ||
| builtin("number", variadic=is_string, minArgs=1)(float) | ||
| @builtin('strip', argument_tests=[is_string]) | ||
| @builtin("strip", argument_tests=[is_string]) | ||
| def strip(s): | ||
| return s.strip() | ||
| @builtin('rstrip', argument_tests=[is_string]) | ||
| @builtin("rstrip", argument_tests=[is_string]) | ||
| def rstrip(s): | ||
| return s.rstrip() | ||
| @builtin('lstrip', argument_tests=[is_string]) | ||
| @builtin("lstrip", argument_tests=[is_string]) | ||
| def lstrip(s): | ||
| return s.lstrip() | ||
| @builtin('join', argument_tests=[is_array, is_string_or_number]) | ||
| @builtin("join", argument_tests=[is_array, is_string_or_number]) | ||
| def join(list, separator): | ||
@@ -125,4 +140,4 @@ # convert potential numbers into strings | ||
| @builtin('split', variadic=is_string_or_number, minArgs=1) | ||
| def split(s, d=''): | ||
| @builtin("split", variadic=is_string_or_number, minArgs=1) | ||
| def split(s, d=""): | ||
| if not d and is_string(s): | ||
@@ -133,24 +148,24 @@ return list(s) | ||
| @builtin('fromNow', variadic=is_string, minArgs=1, needs_context=True) | ||
| @builtin("fromNow", variadic=is_string, minArgs=1, needs_context=True) | ||
| def fromNow_builtin(context, offset, reference=None): | ||
| return fromNow(offset, reference or context.get('now')) | ||
| return fromNow(offset, reference or context.get("now")) | ||
| @builtin('typeof', argument_tests=[anything]) | ||
| @builtin("typeof", argument_tests=[anything]) | ||
| def typeof(v): | ||
| if isinstance(v, bool): | ||
| return 'boolean' | ||
| return "boolean" | ||
| elif isinstance(v, string): | ||
| return 'string' | ||
| return "string" | ||
| elif isinstance(v, (int, float)): | ||
| return 'number' | ||
| return "number" | ||
| elif isinstance(v, list): | ||
| return 'array' | ||
| return "array" | ||
| elif isinstance(v, dict): | ||
| return 'object' | ||
| return "object" | ||
| elif v is None: | ||
| return 'null' | ||
| return "null" | ||
| elif callable(v): | ||
| return 'function' | ||
| return "function" | ||
| @builtin('defined', argument_tests=[is_string], needs_context=True) | ||
| @builtin("defined", argument_tests=[is_string], needs_context=True) | ||
| def defined(context, s): | ||
@@ -157,0 +172,0 @@ if s not in context: |
+40
-33
@@ -6,4 +6,5 @@ from .shared import InterpreterError, string | ||
| def infixExpectationError(operator, expected): | ||
| return InterpreterError('infix: {} expects {} {} {}'. | ||
| format(operator, expected, operator, expected)) | ||
| return InterpreterError( | ||
| "infix: {} expects {} {} {}".format(operator, expected, operator, expected) | ||
| ) | ||
@@ -16,3 +17,3 @@ | ||
| def visit(self, node): | ||
| method_name = 'visit_' + type(node).__name__ | ||
| method_name = "visit_" + type(node).__name__ | ||
| visitor = getattr(self, method_name) | ||
@@ -24,3 +25,3 @@ return visitor(node) | ||
| v = node.token.value | ||
| return float(v) if '.' in v else int(v) | ||
| return float(v) if "." in v else int(v) | ||
| elif node.token.kind == "null": | ||
@@ -41,7 +42,7 @@ return None | ||
| if not is_number(value): | ||
| raise InterpreterError('{} expects {}'.format('unary +', 'number')) | ||
| raise InterpreterError("{} expects {}".format("unary +", "number")) | ||
| return value | ||
| elif node.token.kind == "-": | ||
| if not is_number(value): | ||
| raise InterpreterError('{} expects {}'.format('unary -', 'number')) | ||
| raise InterpreterError("{} expects {}".format("unary -", "number")) | ||
| return -value | ||
@@ -62,8 +63,9 @@ elif node.token.kind == "!": | ||
| if not isinstance(left, (string, int, float)) or isinstance(left, bool): | ||
| raise infixExpectationError('+', 'numbers/strings') | ||
| raise infixExpectationError("+", "numbers/strings") | ||
| if not isinstance(right, (string, int, float)) or isinstance(right, bool): | ||
| raise infixExpectationError('+', 'numbers/strings') | ||
| if type(right) != type(left) and \ | ||
| (isinstance(left, string) or isinstance(right, string)): | ||
| raise infixExpectationError('+', 'numbers/strings') | ||
| raise infixExpectationError("+", "numbers/strings") | ||
| if type(right) != type(left) and ( | ||
| isinstance(left, string) or isinstance(right, string) | ||
| ): | ||
| raise infixExpectationError("+", "numbers/strings") | ||
| return left + right | ||
@@ -97,26 +99,26 @@ elif node.token.kind == "-": | ||
| test_math_operands("**", left, right) | ||
| return right ** left | ||
| return right**left | ||
| elif node.token.value == "in": | ||
| if isinstance(right, dict): | ||
| if not isinstance(left, string): | ||
| raise infixExpectationError('in-object', 'string on left side') | ||
| raise infixExpectationError("in-object", "string on left side") | ||
| elif isinstance(right, string): | ||
| if not isinstance(left, string): | ||
| raise infixExpectationError('in-string', 'string on left side') | ||
| raise infixExpectationError("in-string", "string on left side") | ||
| elif not isinstance(right, list): | ||
| raise infixExpectationError( | ||
| 'in', 'Array, string, or object on right side') | ||
| "in", "Array, string, or object on right side" | ||
| ) | ||
| try: | ||
| return left in right | ||
| except TypeError: | ||
| raise infixExpectationError('in', 'scalar value, collection') | ||
| raise infixExpectationError("in", "scalar value, collection") | ||
| elif node.token.kind == ".": | ||
| if not isinstance(left, dict): | ||
| raise InterpreterError('infix: {} expects {}'.format(".", 'objects')) | ||
| raise InterpreterError("infix: {} expects {}".format(".", "objects")) | ||
| try: | ||
| return left[right] | ||
| except KeyError: | ||
| raise InterpreterError( | ||
| 'object has no property "{}"'.format(right)) | ||
| raise InterpreterError('object has no property "{}"'.format(right)) | ||
@@ -149,3 +151,5 @@ def visit_List(self, node): | ||
| except TypeError: | ||
| raise InterpreterError('cannot perform interval access with non-integers') | ||
| raise InterpreterError( | ||
| "cannot perform interval access with non-integers" | ||
| ) | ||
| else: | ||
@@ -155,10 +159,14 @@ try: | ||
| except IndexError: | ||
| raise InterpreterError('index out of bounds') | ||
| raise InterpreterError("index out of bounds") | ||
| except TypeError: | ||
| raise InterpreterError('should only use integers to access arrays or strings') | ||
| raise InterpreterError( | ||
| "should only use integers to access arrays or strings" | ||
| ) | ||
| if not isinstance(value, dict): | ||
| raise InterpreterError('infix: {} expects {}'.format('"[..]"', 'object, array, or string')) | ||
| raise InterpreterError( | ||
| "infix: {} expects {}".format('"[..]"', "object, array, or string") | ||
| ) | ||
| if not isinstance(left, string): | ||
| raise InterpreterError('object keys must be strings') | ||
| raise InterpreterError("object keys must be strings") | ||
@@ -174,4 +182,3 @@ try: | ||
| except KeyError: | ||
| raise InterpreterError( | ||
| 'unknown context value {}'.format(node.token.value)) | ||
| raise InterpreterError("unknown context value {}".format(node.token.value)) | ||
| return contextValue | ||
@@ -191,4 +198,3 @@ | ||
| else: | ||
| raise InterpreterError( | ||
| '{} is not callable'.format(func_name)) | ||
| raise InterpreterError("{} is not callable".format(func_name)) | ||
@@ -207,5 +213,5 @@ def visit_Object(self, node): | ||
| if not is_number(left): | ||
| raise infixExpectationError(op, 'number') | ||
| raise infixExpectationError(op, "number") | ||
| if not is_number(right): | ||
| raise infixExpectationError(op, 'number') | ||
| raise infixExpectationError(op, "number") | ||
| return | ||
@@ -215,5 +221,6 @@ | ||
| def test_comparison_operands(op, left, right): | ||
| if type(left) != type(right) or \ | ||
| not (isinstance(left, (int, float, string)) and not isinstance(left, bool)): | ||
| raise infixExpectationError(op, 'numbers/strings') | ||
| if type(left) != type(right) or not ( | ||
| isinstance(left, (int, float, string)) and not isinstance(left, bool) | ||
| ): | ||
| raise infixExpectationError(op, "numbers/strings") | ||
| return | ||
@@ -220,0 +227,0 @@ |
+54
-21
@@ -1,2 +0,11 @@ | ||
| from .AST import Primitive, UnaryOp, ContextValue, BinOp, FunctionCall, ValueAccess, Object, List | ||
| from .AST import ( | ||
| Primitive, | ||
| UnaryOp, | ||
| ContextValue, | ||
| BinOp, | ||
| FunctionCall, | ||
| ValueAccess, | ||
| Object, | ||
| List, | ||
| ) | ||
| from collections import namedtuple | ||
@@ -6,13 +15,25 @@ import re | ||
| Token = namedtuple('Token', ['kind', 'value', 'start', 'end']) | ||
| Token = namedtuple("Token", ["kind", "value", "start", "end"]) | ||
| expectedTokens = ["!", "(", "+", "-", "[", "false", "identifier", "null", "number", "string", "true", "{"] | ||
| expectedTokens = [ | ||
| "!", | ||
| "(", | ||
| "+", | ||
| "-", | ||
| "[", | ||
| "false", | ||
| "identifier", | ||
| "null", | ||
| "number", | ||
| "string", | ||
| "true", | ||
| "{", | ||
| ] | ||
| class SyntaxError(TemplateError): | ||
| @classmethod | ||
| def unexpected(cls, got, exp): | ||
| exp = ', '.join(sorted(exp)) | ||
| return cls('Found: {} token, expected one of: {}'.format(got.value, exp)) | ||
| exp = ", ".join(sorted(exp)) | ||
| return cls("Found: {} token, expected one of: {}".format(got.value, exp)) | ||
@@ -27,8 +48,16 @@ | ||
| self.primitivesTokens = ["number", "null", "true", "false", "string"] | ||
| self.operatorsByPriority = [["||"], ["&&"], ["in"], ["==", "!="], ["<", ">", "<=", ">="], ["+", "-"], | ||
| ["*", "/"], ["**"]] | ||
| self.operatorsByPriority = [ | ||
| ["||"], | ||
| ["&&"], | ||
| ["in"], | ||
| ["==", "!="], | ||
| ["<", ">", "<=", ">="], | ||
| ["+", "-"], | ||
| ["*", "/"], | ||
| ["**"], | ||
| ] | ||
| def take_token(self, *kinds): | ||
| if not self.current_token: | ||
| raise SyntaxError('Unexpected end of input') | ||
| raise SyntaxError("Unexpected end of input") | ||
| if kinds and self.current_token.kind not in kinds: | ||
@@ -44,3 +73,3 @@ raise SyntaxError.unexpected(self.current_token, kinds) | ||
| def parse(self, level=0): | ||
| """ expr : logicalAnd (OR logicalAnd)* """ | ||
| """expr : logicalAnd (OR logicalAnd)*""" | ||
| """ logicalAnd : inStatement (AND inStatement)* """ | ||
@@ -73,3 +102,3 @@ """ inStatement : equality (IN equality)* """ | ||
| def parse_property_access_or_func(self): | ||
| """ propertyAccessOrFunc : unit (accessWithBrackets | DOT id | functionCall)* """ | ||
| """propertyAccessOrFunc : unit (accessWithBrackets | DOT id | functionCall)*""" | ||
| node = self.parse_unit() | ||
@@ -96,3 +125,3 @@ token = self.current_token | ||
| if self.current_token is None: | ||
| raise SyntaxError('Unexpected end of input') | ||
| raise SyntaxError("Unexpected end of input") | ||
| node = None | ||
@@ -145,3 +174,3 @@ | ||
| def parse_list(self): | ||
| """ list: LSQAREBRAKET (expr (COMMA expr)*)? RSQAREBRAKET """ | ||
| """list: LSQAREBRAKET (expr (COMMA expr)*)? RSQAREBRAKET""" | ||
| arr = [] | ||
@@ -168,3 +197,3 @@ token = self.current_token | ||
| def parse_access_with_brackets(self, node): | ||
| """ valueAccess : LSQAREBRAKET expr |(expr? COLON expr?) RSQAREBRAKET)""" | ||
| """valueAccess : LSQAREBRAKET expr |(expr? COLON expr?) RSQAREBRAKET)""" | ||
| left = None | ||
@@ -201,3 +230,5 @@ right = None | ||
| while token is not None and (token.kind == "string" or token.kind == "identifier"): | ||
| while token is not None and ( | ||
| token.kind == "string" or token.kind == "identifier" | ||
| ): | ||
| key = token.value | ||
@@ -235,7 +266,7 @@ if token.kind == "string": | ||
| token_patterns = [ | ||
| '({})'.format(self.patterns.get(t, re.escape(t))) | ||
| for t in self.tokens] | ||
| "({})".format(self.patterns.get(t, re.escape(t))) for t in self.tokens | ||
| ] | ||
| if self.ignore: | ||
| token_patterns.append('(?:{})'.format(self.ignore)) | ||
| self.token_re = re.compile('^(?:' + '|'.join(token_patterns) + ')') | ||
| token_patterns.append("(?:{})".format(self.ignore)) | ||
| self.token_re = re.compile("^(?:" + "|".join(token_patterns) + ")") | ||
@@ -251,3 +282,4 @@ def generate_tokens(self, source): | ||
| raise SyntaxError( | ||
| "Unexpected input for '{}' at '{}'".format(source, remainder)) | ||
| "Unexpected input for '{}' at '{}'".format(source, remainder) | ||
| ) | ||
| break | ||
@@ -264,2 +296,3 @@ offset += mo.end() | ||
| start=start, | ||
| end=offset) | ||
| end=offset, | ||
| ) |
+161
-117
@@ -14,11 +14,12 @@ from __future__ import absolute_import, print_function, unicode_literals | ||
| operators = {} | ||
| IDENTIFIER_RE = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*$') | ||
| IDENTIFIER_RE = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") | ||
| class SyntaxError(TemplateError): | ||
| @classmethod | ||
| def unexpected(cls, got): | ||
| return cls('Found: {} token, expected one of: !=, &&, (, *, **, +, -, ., /, <, <=, ==, >, >=, [, in,' | ||
| ' ||'.format(got.value)) | ||
| return cls( | ||
| "Found: {} token, expected one of: !=, &&, (, *, **, +, -, ., /, <, <=, ==, >, >=, [, in," | ||
| " ||".format(got.value) | ||
| ) | ||
@@ -30,2 +31,3 @@ | ||
| return fn | ||
| return wrap | ||
@@ -35,17 +37,44 @@ | ||
| tokenizer = Tokenizer( | ||
| '\\s+', | ||
| "\\s+", | ||
| { | ||
| 'number': '[0-9]+(?:\\.[0-9]+)?', | ||
| 'identifier': '[a-zA-Z_][a-zA-Z_0-9]*', | ||
| 'string': '\'[^\']*\'|"[^"]*"', | ||
| "number": "[0-9]+(?:\\.[0-9]+)?", | ||
| "identifier": "[a-zA-Z_][a-zA-Z_0-9]*", | ||
| "string": "'[^']*'|\"[^\"]*\"", | ||
| # avoid matching these as prefixes of identifiers e.g., `insinutations` | ||
| 'true': 'true(?![a-zA-Z_0-9])', | ||
| 'false': 'false(?![a-zA-Z_0-9])', | ||
| 'in': 'in(?![a-zA-Z_0-9])', | ||
| 'null': 'null(?![a-zA-Z_0-9])', | ||
| "true": "true(?![a-zA-Z_0-9])", | ||
| "false": "false(?![a-zA-Z_0-9])", | ||
| "in": "in(?![a-zA-Z_0-9])", | ||
| "null": "null(?![a-zA-Z_0-9])", | ||
| }, | ||
| [ | ||
| '**', '+', '-', '*', '/', '[', ']', '.', '(', ')', '{', '}', ':', ',', | ||
| '>=', '<=', '<', '>', '==', '!=', '!', '&&', '||', 'true', 'false', 'in', | ||
| 'null', 'number', 'identifier', 'string', | ||
| "**", | ||
| "+", | ||
| "-", | ||
| "*", | ||
| "/", | ||
| "[", | ||
| "]", | ||
| ".", | ||
| "(", | ||
| ")", | ||
| "{", | ||
| "}", | ||
| ":", | ||
| ",", | ||
| ">=", | ||
| "<=", | ||
| "<", | ||
| ">", | ||
| "==", | ||
| "!=", | ||
| "!", | ||
| "&&", | ||
| "||", | ||
| "true", | ||
| "false", | ||
| "in", | ||
| "null", | ||
| "number", | ||
| "identifier", | ||
| "string", | ||
| ], | ||
@@ -76,3 +105,3 @@ ) | ||
| _interpolation_start_re = re.compile(r'\$?\${') | ||
| _interpolation_start_re = re.compile(r"\$?\${") | ||
@@ -88,9 +117,12 @@ | ||
| while True: | ||
| result.append(string[:mo.start()]) | ||
| if mo.group() != '$${': | ||
| string = string[mo.end():] | ||
| parsed, offset = parse_until_terminator(string, context, '}') | ||
| result.append(string[: mo.start()]) | ||
| if mo.group() != "$${": | ||
| string = string[mo.end() :] | ||
| parsed, offset = parse_until_terminator(string, context, "}") | ||
| if isinstance(parsed, (list, dict)): | ||
| raise TemplateError( | ||
| "interpolation of '{}' produced an array or object".format(string[:offset])) | ||
| "interpolation of '{}' produced an array or object".format( | ||
| string[:offset] | ||
| ) | ||
| ) | ||
| if parsed is None: | ||
@@ -100,6 +132,6 @@ result.append("") | ||
| result.append(to_str(parsed)) | ||
| string = string[offset + 1:] | ||
| string = string[offset + 1 :] | ||
| else: # found `$${` | ||
| result.append('${') | ||
| string = string[mo.end():] | ||
| result.append("${") | ||
| string = string[mo.end() :] | ||
@@ -110,3 +142,3 @@ mo = _interpolation_start_re.search(string) | ||
| break | ||
| return ''.join(result) | ||
| return "".join(result) | ||
@@ -117,23 +149,25 @@ | ||
| combined = "|".join(allowed) + "$" | ||
| unknownKeys = [key for key in sorted(template) | ||
| if not re.match(combined, key)] | ||
| unknownKeys = [key for key in sorted(template) if not re.match(combined, key)] | ||
| if unknownKeys: | ||
| raise TemplateError(allowed[0].replace('\\', '') + | ||
| " has undefined properties: " + " ".join(unknownKeys)) | ||
| raise TemplateError( | ||
| allowed[0].replace("\\", "") | ||
| + " has undefined properties: " | ||
| + " ".join(unknownKeys) | ||
| ) | ||
| @operator('$eval') | ||
| @operator("$eval") | ||
| def eval(template, context): | ||
| checkUndefinedProperties(template, [r'\$eval']) | ||
| if not isinstance(template['$eval'], string): | ||
| checkUndefinedProperties(template, [r"\$eval"]) | ||
| if not isinstance(template["$eval"], string): | ||
| raise TemplateError("$eval must be given a string expression") | ||
| return parse(template['$eval'], context) | ||
| return parse(template["$eval"], context) | ||
| @operator('$flatten') | ||
| @operator("$flatten") | ||
| def flatten(template, context): | ||
| checkUndefinedProperties(template, [r'\$flatten']) | ||
| value = renderValue(template['$flatten'], context) | ||
| checkUndefinedProperties(template, [r"\$flatten"]) | ||
| value = renderValue(template["$flatten"], context) | ||
| if not isinstance(value, list): | ||
| raise TemplateError('$flatten value must evaluate to an array') | ||
| raise TemplateError("$flatten value must evaluate to an array") | ||
@@ -147,11 +181,12 @@ def gen(): | ||
| yield e | ||
| return list(gen()) | ||
| @operator('$flattenDeep') | ||
| @operator("$flattenDeep") | ||
| def flattenDeep(template, context): | ||
| checkUndefinedProperties(template, [r'\$flattenDeep']) | ||
| value = renderValue(template['$flattenDeep'], context) | ||
| checkUndefinedProperties(template, [r"\$flattenDeep"]) | ||
| value = renderValue(template["$flattenDeep"], context) | ||
| if not isinstance(value, list): | ||
| raise TemplateError('$flattenDeep value must evaluate to an array') | ||
| raise TemplateError("$flattenDeep value must evaluate to an array") | ||
@@ -169,8 +204,11 @@ def gen(value): | ||
| @operator('$fromNow') | ||
| @operator("$fromNow") | ||
| def fromNow(template, context): | ||
| checkUndefinedProperties(template, [r'\$fromNow', 'from']) | ||
| offset = renderValue(template['$fromNow'], context) | ||
| reference = renderValue( | ||
| template['from'], context) if 'from' in template else context.get('now') | ||
| checkUndefinedProperties(template, [r"\$fromNow", "from"]) | ||
| offset = renderValue(template["$fromNow"], context) | ||
| reference = ( | ||
| renderValue(template["from"], context) | ||
| if "from" in template | ||
| else context.get("now") | ||
| ) | ||
@@ -182,11 +220,11 @@ if not isinstance(offset, string): | ||
| @operator('$if') | ||
| @operator("$if") | ||
| def ifConstruct(template, context): | ||
| checkUndefinedProperties(template, [r'\$if', 'then', 'else']) | ||
| condition = parse(template['$if'], context) | ||
| checkUndefinedProperties(template, [r"\$if", "then", "else"]) | ||
| condition = parse(template["$if"], context) | ||
| try: | ||
| if condition: | ||
| rv = template['then'] | ||
| rv = template["then"] | ||
| else: | ||
| rv = template['else'] | ||
| rv = template["else"] | ||
| except KeyError: | ||
@@ -197,19 +235,19 @@ return DeleteMarker | ||
| @operator('$json') | ||
| @operator("$json") | ||
| def jsonConstruct(template, context): | ||
| checkUndefinedProperties(template, [r'\$json']) | ||
| value = renderValue(template['$json'], context) | ||
| checkUndefinedProperties(template, [r"\$json"]) | ||
| value = renderValue(template["$json"], context) | ||
| if containsFunctions(value): | ||
| raise TemplateError('evaluated template contained uncalled functions') | ||
| return json.dumps(value, separators=(',', ':'), sort_keys=True, ensure_ascii=False) | ||
| raise TemplateError("evaluated template contained uncalled functions") | ||
| return json.dumps(value, separators=(",", ":"), sort_keys=True, ensure_ascii=False) | ||
| @operator('$let') | ||
| @operator("$let") | ||
| def let(template, context): | ||
| checkUndefinedProperties(template, [r'\$let', 'in']) | ||
| if not isinstance(template['$let'], dict): | ||
| checkUndefinedProperties(template, [r"\$let", "in"]) | ||
| if not isinstance(template["$let"], dict): | ||
| raise TemplateError("$let value must be an object") | ||
| subcontext = context.copy() | ||
| initial_result = renderValue(template['$let'], context) | ||
| initial_result = renderValue(template["$let"], context) | ||
| if not isinstance(initial_result, dict): | ||
@@ -219,7 +257,9 @@ raise TemplateError("$let value must be an object") | ||
| if not IDENTIFIER_RE.match(k): | ||
| raise TemplateError("top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/") | ||
| raise TemplateError( | ||
| "top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/" | ||
| ) | ||
| else: | ||
| subcontext[k] = v | ||
| try: | ||
| in_expression = template['in'] | ||
| in_expression = template["in"] | ||
| except KeyError: | ||
@@ -230,7 +270,7 @@ raise TemplateError("$let operator requires an `in` clause") | ||
| @operator('$map') | ||
| @operator("$map") | ||
| def map(template, context): | ||
| EACH_RE = r'each\([a-zA-Z_][a-zA-Z0-9_]*(,\s*([a-zA-Z_][a-zA-Z0-9_]*))?\)' | ||
| checkUndefinedProperties(template, [r'\$map', EACH_RE]) | ||
| value = renderValue(template['$map'], context) | ||
| EACH_RE = r"each\([a-zA-Z_][a-zA-Z0-9_]*(,\s*([a-zA-Z_][a-zA-Z0-9_]*))?\)" | ||
| checkUndefinedProperties(template, [r"\$map", EACH_RE]) | ||
| value = renderValue(template["$map"], context) | ||
| if not isinstance(value, list) and not isinstance(value, dict): | ||
@@ -241,8 +281,7 @@ raise TemplateError("$map value must evaluate to an array or object") | ||
| each_keys = [k for k in template if k.startswith('each(')] | ||
| each_keys = [k for k in template if k.startswith("each(")] | ||
| if len(each_keys) != 1: | ||
| raise TemplateError( | ||
| "$map requires exactly one other property, each(..)") | ||
| raise TemplateError("$map requires exactly one other property, each(..)") | ||
| each_key = each_keys[0] | ||
| each_args = [x.strip() for x in each_key[5:-1].split(',')] | ||
| each_args = [x.strip() for x in each_key[5:-1].split(",")] | ||
| each_var = each_args[0] | ||
@@ -259,9 +298,10 @@ each_idx = each_args[1] if len(each_args) > 1 else None | ||
| else: | ||
| subcontext[each_var] = elt['val'] if is_obj else elt | ||
| subcontext[each_idx] = elt['key'] if is_obj else i | ||
| subcontext[each_var] = elt["val"] if is_obj else elt | ||
| subcontext[each_idx] = elt["key"] if is_obj else i | ||
| elt = renderValue(each_template, subcontext) | ||
| if elt is not DeleteMarker: | ||
| yield elt | ||
| if is_obj: | ||
| value = [{'key': v[0], 'val': v[1]} for v in value.items()] | ||
| value = [{"key": v[0], "val": v[1]} for v in value.items()] | ||
| v = dict() | ||
@@ -271,3 +311,6 @@ for e in gen(value): | ||
| raise TemplateError( | ||
| "$map on objects expects {0} to evaluate to an object".format(each_key)) | ||
| "$map on objects expects {0} to evaluate to an object".format( | ||
| each_key | ||
| ) | ||
| ) | ||
| v.update(e) | ||
@@ -279,13 +322,13 @@ return v | ||
| @operator('$match') | ||
| @operator("$match") | ||
| def matchConstruct(template, context): | ||
| checkUndefinedProperties(template, [r'\$match']) | ||
| checkUndefinedProperties(template, [r"\$match"]) | ||
| if not isinstance(template['$match'], dict): | ||
| if not isinstance(template["$match"], dict): | ||
| raise TemplateError("$match can evaluate objects only") | ||
| result = [] | ||
| for condition in sorted(template['$match']): | ||
| for condition in sorted(template["$match"]): | ||
| if parse(condition, context): | ||
| result.append(renderValue(template['$match'][condition], context)) | ||
| result.append(renderValue(template["$match"][condition], context)) | ||
@@ -295,13 +338,13 @@ return result | ||
| @operator('$switch') | ||
| @operator("$switch") | ||
| def switch(template, context): | ||
| checkUndefinedProperties(template, [r'\$switch']) | ||
| checkUndefinedProperties(template, [r"\$switch"]) | ||
| if not isinstance(template['$switch'], dict): | ||
| if not isinstance(template["$switch"], dict): | ||
| raise TemplateError("$switch can evaluate objects only") | ||
| result = [] | ||
| for condition in template['$switch']: | ||
| if not condition == '$default' and parse(condition, context): | ||
| result.append(renderValue(template['$switch'][condition], context)) | ||
| for condition in template["$switch"]: | ||
| if not condition == "$default" and parse(condition, context): | ||
| result.append(renderValue(template["$switch"][condition], context)) | ||
@@ -312,4 +355,4 @@ if len(result) > 1: | ||
| if len(result) == 0: | ||
| if '$default' in template['$switch']: | ||
| result.append(renderValue(template['$switch']['$default'], context)) | ||
| if "$default" in template["$switch"]: | ||
| result.append(renderValue(template["$switch"]["$default"], context)) | ||
@@ -319,9 +362,8 @@ return result[0] if len(result) > 0 else DeleteMarker | ||
| @operator('$merge') | ||
| @operator("$merge") | ||
| def merge(template, context): | ||
| checkUndefinedProperties(template, [r'\$merge']) | ||
| value = renderValue(template['$merge'], context) | ||
| checkUndefinedProperties(template, [r"\$merge"]) | ||
| value = renderValue(template["$merge"], context) | ||
| if not isinstance(value, list) or not all(isinstance(e, dict) for e in value): | ||
| raise TemplateError( | ||
| "$merge value must evaluate to an array of objects") | ||
| raise TemplateError("$merge value must evaluate to an array of objects") | ||
| v = dict() | ||
@@ -333,9 +375,8 @@ for e in value: | ||
| @operator('$mergeDeep') | ||
| @operator("$mergeDeep") | ||
| def merge(template, context): | ||
| checkUndefinedProperties(template, [r'\$mergeDeep']) | ||
| value = renderValue(template['$mergeDeep'], context) | ||
| checkUndefinedProperties(template, [r"\$mergeDeep"]) | ||
| value = renderValue(template["$mergeDeep"], context) | ||
| if not isinstance(value, list) or not all(isinstance(e, dict) for e in value): | ||
| raise TemplateError( | ||
| "$mergeDeep value must evaluate to an array of objects") | ||
| raise TemplateError("$mergeDeep value must evaluate to an array of objects") | ||
@@ -354,2 +395,3 @@ def merge(l, r): | ||
| return r | ||
| if len(value) == 0: | ||
@@ -360,6 +402,6 @@ return {} | ||
| @operator('$reverse') | ||
| @operator("$reverse") | ||
| def reverse(template, context): | ||
| checkUndefinedProperties(template, [r'\$reverse']) | ||
| value = renderValue(template['$reverse'], context) | ||
| checkUndefinedProperties(template, [r"\$reverse"]) | ||
| value = renderValue(template["$reverse"], context) | ||
| if not isinstance(value, list): | ||
@@ -370,12 +412,12 @@ raise TemplateError("$reverse value must evaluate to an array of objects") | ||
| @operator('$sort') | ||
| @operator("$sort") | ||
| def sort(template, context): | ||
| BY_RE = r'by\([a-zA-Z_][a-zA-Z0-9_]*\)' | ||
| checkUndefinedProperties(template, [r'\$sort', BY_RE]) | ||
| value = renderValue(template['$sort'], context) | ||
| BY_RE = r"by\([a-zA-Z_][a-zA-Z0-9_]*\)" | ||
| checkUndefinedProperties(template, [r"\$sort", BY_RE]) | ||
| value = renderValue(template["$sort"], context) | ||
| if not isinstance(value, list): | ||
| raise TemplateError('$sorted values to be sorted must have the same type') | ||
| raise TemplateError("$sorted values to be sorted must have the same type") | ||
| # handle by(..) if given, applying the schwartzian transform | ||
| by_keys = [k for k in template if k.startswith('by(')] | ||
| by_keys = [k for k in template if k.startswith("by(")] | ||
| if len(by_keys) == 1: | ||
@@ -391,2 +433,3 @@ by_key = by_keys[0] | ||
| yield parse(by_expr, subcontext), e | ||
| to_sort = list(xform()) | ||
@@ -396,3 +439,3 @@ elif len(by_keys) == 0: | ||
| else: | ||
| raise TemplateError('only one by(..) is allowed') | ||
| raise TemplateError("only one by(..) is allowed") | ||
@@ -405,5 +448,5 @@ # check types | ||
| if eltype in (list, dict, bool, type(None)): | ||
| raise TemplateError('$sorted values to be sorted must have the same type') | ||
| raise TemplateError("$sorted values to be sorted must have the same type") | ||
| if not all(isinstance(e[0], eltype) for e in to_sort): | ||
| raise TemplateError('$sorted values to be sorted must have the same type') | ||
| raise TemplateError("$sorted values to be sorted must have the same type") | ||
@@ -415,3 +458,3 @@ # unzip the schwartzian transform | ||
| def containsFunctions(rendered): | ||
| if hasattr(rendered, '__call__'): | ||
| if hasattr(rendered, "__call__"): | ||
| return True | ||
@@ -445,7 +488,6 @@ elif isinstance(rendered, list): | ||
| for k, v in viewitems(template): | ||
| if k.startswith('$$'): | ||
| if k.startswith("$$"): | ||
| k = k[1:] | ||
| elif k.startswith('$') and IDENTIFIER_RE.match(k[1:]): | ||
| raise TemplateError( | ||
| '$<identifier> is reserved; use $$<identifier>') | ||
| elif k.startswith("$") and IDENTIFIER_RE.match(k[1:]): | ||
| raise TemplateError("$<identifier> is reserved; use $$<identifier>") | ||
| else: | ||
@@ -458,11 +500,13 @@ k = interpolate(k, context) | ||
| if IDENTIFIER_RE.match(k): | ||
| e.add_location('.{}'.format(k)) | ||
| e.add_location(".{}".format(k)) | ||
| else: | ||
| e.add_location('[{}]'.format(json.dumps(k))) | ||
| e.add_location("[{}]".format(json.dumps(k))) | ||
| raise | ||
| if v is not DeleteMarker: | ||
| yield k, v | ||
| return dict(updated()) | ||
| elif isinstance(template, list): | ||
| def updated(): | ||
@@ -475,3 +519,3 @@ for i, e in enumerate(template): | ||
| except JSONTemplateError as e: | ||
| e.add_location('[{}]'.format(i)) | ||
| e.add_location("[{}]".format(i)) | ||
| raise | ||
@@ -478,0 +522,0 @@ |
+37
-36
@@ -20,7 +20,6 @@ from __future__ import absolute_import, print_function, unicode_literals | ||
| def __str__(self): | ||
| location = ' at template' + ''.join(self.location) | ||
| location = " at template" + "".join(self.location) | ||
| return "{}{}: {}".format( | ||
| self.__class__.__name__, | ||
| location if self.location else '', | ||
| self.args[0]) | ||
| self.__class__.__name__, location if self.location else "", self.args[0] | ||
| ) | ||
@@ -37,12 +36,15 @@ | ||
| # Regular expression matching: X days Y hours Z minutes | ||
| # todo: support hr, wk, yr | ||
| FROMNOW_RE = re.compile(''.join([ | ||
| r'^(\s*(?P<years>\d+)\s*y(ears?)?)?', | ||
| r'(\s*(?P<months>\d+)\s*mo(nths?)?)?', | ||
| r'(\s*(?P<weeks>\d+)\s*w(eeks?)?)?', | ||
| r'(\s*(?P<days>\d+)\s*d(ays?)?)?', | ||
| r'(\s*(?P<hours>\d+)\s*h(ours?)?)?', | ||
| r'(\s*(?P<minutes>\d+)\s*m(in(utes?)?)?)?\s*', | ||
| r'(\s*(?P<seconds>\d+)\s*s(ec(onds?)?)?)?\s*$', | ||
| ])) | ||
| FROMNOW_RE = re.compile( | ||
| "".join( | ||
| [ | ||
| r"^(\s*(?P<years>\d+)\s*(years|year|yr|y))?", | ||
| r"(\s*(?P<months>\d+)\s*(months|month|mo))?", | ||
| r"(\s*(?P<weeks>\d+)\s*(weeks|week|wk|w))?", | ||
| r"(\s*(?P<days>\d+)\s*(days|day|d))?", | ||
| r"(\s*(?P<hours>\d+)\s*(hours|hour|hr|h))?", | ||
| r"(\s*(?P<minutes>\d+)\s*(minutes|minute|min|m))?", | ||
| r"(\s*(?P<seconds>\d+)\s*(seconds|second|sec|s))?\s*$", | ||
| ] | ||
| ) | ||
| ) | ||
@@ -55,6 +57,6 @@ | ||
| offset = offset.lstrip() | ||
| if offset.startswith('-'): | ||
| if offset.startswith("-"): | ||
| future = False | ||
| offset = offset[1:].lstrip() | ||
| if offset.startswith('+'): | ||
| if offset.startswith("+"): | ||
| offset = offset[1:].lstrip() | ||
@@ -65,3 +67,3 @@ | ||
| if m is None: | ||
| raise ValueError("offset string: '%s' does not parse" % offset) | ||
| raise TemplateError("offset string: '%s' does not parse" % offset) | ||
@@ -74,18 +76,18 @@ # In order to calculate years and months we need to calculate how many days | ||
| seconds = 0 | ||
| if m.group('years'): | ||
| if m.group("years"): | ||
| # forget leap years, a year is 365 days | ||
| years = int(m.group('years')) | ||
| years = int(m.group("years")) | ||
| days += 365 * years | ||
| if m.group('months'): | ||
| if m.group("months"): | ||
| # assume "month" means 30 days | ||
| months = int(m.group('months')) | ||
| months = int(m.group("months")) | ||
| days += 30 * months | ||
| days += int(m.group('days') or 0) | ||
| hours += int(m.group('hours') or 0) | ||
| minutes += int(m.group('minutes') or 0) | ||
| seconds += int(m.group('seconds') or 0) | ||
| days += int(m.group("days") or 0) | ||
| hours += int(m.group("hours") or 0) | ||
| minutes += int(m.group("minutes") or 0) | ||
| seconds += int(m.group("seconds") or 0) | ||
| # Offset datetime from utc | ||
| delta = datetime.timedelta( | ||
| weeks=int(m.group('weeks') or 0), | ||
| weeks=int(m.group("weeks") or 0), | ||
| days=days, | ||
@@ -98,4 +100,3 @@ hours=hours, | ||
| if isinstance(reference, string): | ||
| reference = datetime.datetime.strptime( | ||
| reference, '%Y-%m-%dT%H:%M:%S.%fZ') | ||
| reference = datetime.datetime.strptime(reference, "%Y-%m-%dT%H:%M:%S.%fZ") | ||
| elif reference is None: | ||
@@ -106,3 +107,3 @@ reference = datetime.datetime.utcnow() | ||
| datefmt_re = re.compile(r'(\.[0-9]{3})[0-9]*(\+00:00)?') | ||
| datefmt_re = re.compile(r"(\.[0-9]{3})[0-9]*(\+00:00)?") | ||
@@ -112,7 +113,7 @@ | ||
| if isinstance(v, bool): | ||
| return {True: 'true', False: 'false'}[v] | ||
| return {True: "true", False: "false"}[v] | ||
| elif isinstance(v, list): | ||
| return ','.join(to_str(e) for e in v) | ||
| return ",".join(to_str(e) for e in v) | ||
| elif v is None: | ||
| return 'null' | ||
| return "null" | ||
| elif isinstance(v, string): | ||
@@ -127,9 +128,9 @@ return v | ||
| try: | ||
| string = date.isoformat(timespec='microseconds') | ||
| string = date.isoformat(timespec="microseconds") | ||
| # py2.7 to py3.5 does not have timespec | ||
| except TypeError as e: | ||
| string = date.isoformat() | ||
| if string.find('.') == -1: | ||
| string += '.000' | ||
| string = datefmt_re.sub(r'\1Z', string) | ||
| if string.find(".") == -1: | ||
| string += ".000" | ||
| string = datefmt_re.sub(r"\1Z", string) | ||
| return string | ||
@@ -136,0 +137,0 @@ |
+3
-2
@@ -9,2 +9,3 @@ import sys | ||
| """Create a base class with a metaclass.""" | ||
| # This requires a bit of explanation: the basic idea is to make a dummy | ||
@@ -14,8 +15,8 @@ # metaclass for one level of class instantiation that replaces itself with | ||
| class metaclass(meta): | ||
| def __new__(cls, name, this_bases, d): | ||
| return meta(name, bases, d) | ||
| return type.__new__(metaclass, 'temporary_class', (), {}) | ||
| return type.__new__(metaclass, "temporary_class", (), {}) | ||
| # https://github.com/benjaminp/six/blob/2c3492a9f16d294cd5e6b43d6110c5a3a2e58b4c/six.py#L578 | ||
@@ -22,0 +23,0 @@ if sys.version_info[0] == 3: |
+1
-1
| Metadata-Version: 2.1 | ||
| Name: json-e | ||
| Version: 4.5.3 | ||
| Version: 4.6.0 | ||
| Summary: A data-structure parameterization system written for embedding context in JSON objects | ||
@@ -5,0 +5,0 @@ Home-page: https://json-e.js.org |
+16
-13
@@ -5,26 +5,29 @@ import json | ||
| version = "4.5.3" | ||
| version = "4.6.0" | ||
| description='A data-structure parameterization system written for embedding context in JSON objects' | ||
| description = "A data-structure parameterization system written for embedding context in JSON objects" | ||
| long_description = '''\ | ||
| long_description = """\ | ||
| {description}. | ||
| See https://json-e.js.org for usage information. | ||
| '''.format(description=description) | ||
| """.format( | ||
| description=description | ||
| ) | ||
| setup(name='json-e', | ||
| setup( | ||
| name="json-e", | ||
| version=version, | ||
| description=description, | ||
| long_description=long_description, | ||
| author='Dustin J. Mitchell', | ||
| url='https://json-e.js.org', | ||
| author_email='dustin@mozilla.com', | ||
| packages=['jsone'], | ||
| license='MPL2', | ||
| author="Dustin J. Mitchell", | ||
| url="https://json-e.js.org", | ||
| author_email="dustin@mozilla.com", | ||
| packages=["jsone"], | ||
| license="MPL2", | ||
| extras_require={ | ||
| 'release': [ | ||
| 'towncrier', | ||
| "release": [ | ||
| "towncrier", | ||
| ], | ||
| } | ||
| }, | ||
| ) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
46017
2.25%1184
8.82%