def testStacking(self): s = scope.ScopeStack({"x": 1}, {"x:": 2}) s = scope.ScopeStack(s, {"x": 3}) # Stack remains flat. self.assertEqual(s.locals, {"x": 3}) self.assertEqual(s.globals, {"x": 1})
def infer(query, replacements=None, root_type=None, libs=("stdcore", "stdmath")): """Determine the type of the query's output without actually running it. Arguments: query: A query object or string with the query. replacements: Built-time parameters to the query, either as dict or as an array (for positional interpolation). root_type: The types of variables to be supplied to the query inference. libs: What standard libraries should be taken into account for the inference. Returns: The type of the query's output, if it can be determined. If undecidable, returns efilter.protocol.AnyType. NOTE: The inference returns the type of a row in the results, not of the actual Python object returned by 'apply'. For example, if a query returns multiple rows, each one of which is an integer, the type of the output is considered to be int, not a collection of rows. Examples: infer("5 + 5") # -> INumber infer("SELECT * FROM people WHERE age > 10") # -> AnyType # If root_type implements the IStructured reflection API: infer("SELECT * FROM people WHERE age > 10", root_type=...) # -> dict """ # Always make the scope stack start with stdcore. if root_type: type_scope = scope.ScopeStack(std_core.MODULE, root_type) else: type_scope = scope.ScopeStack(std_core.MODULE) stdcore_included = False for lib in libs: if lib == "stdcore": stdcore_included = True continue module = std_core.LibraryModule.ALL_MODULES.get(lib) if not module: raise TypeError("No standard library module %r." % lib) type_scope = scope.ScopeStack(module, type_scope) if not stdcore_included: raise TypeError("'stdcore' must always be included.") query = q.Query(query, params=replacements) return infer_type.infer_type(query, type_scope)
def infer_type(query, scope=None): # Always include stdcore at the top level. if scope: scope = s.ScopeStack(std_core.MODULE, scope) else: scope = s.ScopeStack(std_core.MODULE) try: return infer_type(query.root, scope) except errors.EfilterError as error: error.query = query.source raise
def solve_bind(expr, vars): """Build a RowTuple from key/value pairs under the bind. The Bind subtree is arranged as follows: Bind | First KV Pair | | First Key Expression | | First Value Expression | Second KV Pair | | Second Key Expression | | Second Value Expression Etc... """ local_scope = vars values = [] keys = [] for pair in expr.children: key = solve(pair.key, local_scope).value keys.append(key) value = solve(pair.value, local_scope).value values.append(value) local_scope = scope.ScopeStack(local_scope, {key: value}) result = {} for k, v in zip(keys, values): result[k] = v return Result(result, ())
def solve_bind(expr, vars): """Build a RowTuple from key/value pairs under the bind. The Bind subtree is arranged as follows: Bind | First KV Pair | | First Key Expression | | First Value Expression | Second KV Pair | | Second Key Expression | | Second Value Expression Etc... As we evaluate the subtree, each subsequent KV pair is evaluated with the all previous bingings already in scope. For example: bind(x: 5, y: x + 5) # Will bind y = 10 because x is already available. """ value_expressions = [] keys = [] for pair in expr.children: keys.append(solve(pair.key, vars).value) value_expressions.append(pair.value) result = row_tuple.RowTuple(ordered_columns=keys) intermediate_scope = scope.ScopeStack(vars, result) for idx, value_expression in enumerate(value_expressions): value = solve(value_expression, intermediate_scope).value # Update the intermediate bindings so as to make earlier bindings # already available to the next child-expression. result[keys[idx]] = value return Result(result, ())
def solve_query(query, vars): # Standard library must always be included. Others are optional, and the # caller can add them to vars using ScopeStack. vars = scope.ScopeStack(std_core.MODULE, vars) try: return solve(query.root, vars) except errors.EfilterError as error: if not error.query: error.query = query.source raise
def __nest_scope(expr, outer, inner): try: return scope.ScopeStack(outer, inner) except TypeError: if protocol.implements(inner, applicative.IApplicative): raise errors.EfilterTypeError( root=expr, query=expr.source, message="Attempting to use a function %r as an object." % inner) raise errors.EfilterTypeError( root=expr, query=expr.source, message="Attempting to use %r as an object (IStructured)." % inner)
def infer_type(expr, scope): if not isinstance(scope, s.ScopeStack): scope = s.ScopeStack(scope) return scope.reflect(expr.value) or protocol.AnyType
def infer_type(expr, scope): t = infer_type(expr.context, scope) return infer_type(expr.expression, s.ScopeStack(scope, t))
def apply(query, replacements=None, vars=None, allow_io=False, libs=("stdcore", "stdmath")): """Run 'query' on 'vars' and return the result(s). Arguments: query: A query object or string with the query. replacements: Built-time parameters to the query, either as dict or as an array (for positional interpolation). vars: The variables to be supplied to the query solver. allow_io: (Default: False) Include 'stdio' and allow IO functions. libs: Iterable of library modules to include, given as strings. Default: ('stdcore', 'stdmath') For full list of bundled libraries, see efilter.stdlib. Note: 'stdcore' must always be included. WARNING: Including 'stdio' must be done in conjunction with 'allow_io'. This is to make enabling IO explicit. 'allow_io' implies that 'stdio' should be included and so adding it to libs is actually not required. Notes on IO: If allow_io is set to True then 'stdio' will be included and the EFILTER query will be allowed to read files from disk. Use this with caution. If the query returns a lazily-evaluated result that depends on reading from a file (for example, filtering a CSV file) then the file descriptor will remain open until the returned result is deallocated. The caller is responsible for releasing the result when it's no longer needed. Returns: The result of evaluating the query. The type of the output will depend on the query, and can be predicted using 'infer' (provided reflection callbacks are implemented). In the common case of a SELECT query the return value will be an iterable of filtered data (actually an object implementing IRepeated, as well as __iter__.) A word on cardinality of the return value: Types in EFILTER always refer to a scalar. If apply returns more than one value, the type returned by 'infer' will refer to the type of the value inside the returned container. If you're unsure whether your query returns one or more values (rows), use the 'getvalues' function. Raises: efilter.errors.EfilterError if there are issues with the query. Examples: apply("5 + 5") # -> 10 apply("SELECT * FROM people WHERE age > 10", vars={"people":({"age": 10, "name": "Bob"}, {"age": 20, "name": "Alice"}, {"age": 30, "name": "Eve"})) # This will replace the question mark (?) with the string "Bob" in a # safe manner, preventing SQL injection. apply("SELECT * FROM people WHERE name = ?", replacements=["Bob"], ...) """ if vars is None: vars = {} if allow_io: libs = list(libs) libs.append("stdio") query = q.Query(query, params=replacements) stdcore_included = False for lib in libs: if lib == "stdcore": stdcore_included = True # 'solve' always includes this automatically - we don't have a say # in the matter. continue if lib == "stdio" and not allow_io: raise ValueError("Attempting to include 'stdio' but IO not " "enabled. Pass allow_io=True.") module = std_core.LibraryModule.ALL_MODULES.get(lib) if not lib: raise ValueError("There is no standard library module %r." % lib) vars = scope.ScopeStack(module, vars) if not stdcore_included: raise ValueError("EFILTER cannot work without standard lib 'stdcore'.") results = solve.solve(query, vars).value return results
def testResolutionOrder(self): s = scope.ScopeStack({"x": 1}, {"x:": 2}, {"x": 3}) self.assertEqual(s.locals, {"x": 3}) self.assertEqual(s.globals, {"x": 1}) self.assertEqual(s.resolve("x"), 3)