Skip to content

Commit

Permalink
support named function parameters
Browse files Browse the repository at this point in the history
this allows for syntax like:

OData.CSC.Intersects(area=geography'SRID=4326;Point(1 2)')

-add NamedParam ast Node (with name, param attrs)
-add '=' literal
-add list_named_parameters grammar (similar to list_expr)
-only check args for functions with namespace () or ('geo',)
-in django_q visitor, passed NamedParams as keyword args

externally, we can now use the following to support above query:

class AstToDjangoQVisitorCSC(AstToDjangoQVisitor):
    def djangofunc_odata__csc__intersects(self, area):
        return super().djangofunc_geo__intersects(ast.Identifier('footprint'), area)
  • Loading branch information
srepmub committed Nov 30, 2023
1 parent 52069fe commit 7d62b9c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 19 deletions.
5 changes: 5 additions & 0 deletions odata_query/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ class UnaryOp(_Node):
###############################################################################
# Function calls
###############################################################################
@dataclass(frozen=True)
class NamedParam(_Node):
name: Identifier
param: _Node

@dataclass(frozen=True)
class Call(_Node):
func: Identifier
Expand Down
10 changes: 9 additions & 1 deletion odata_query/django/django_q.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,15 @@ def visit_Call(self, node: ast.Call) -> Union[Expression, Q]:
except AttributeError:
raise ex.UnsupportedFunctionException(func_name)

res = q_gen(*node.args)
args = []
kwargs = {}
for arg in node.args:
if isinstance(arg, ast.NamedParam):
kwargs[arg.name.name] = arg.param
else:
args.append(arg)

res = q_gen(*args, **kwargs)
return res

def visit_CollectionLambda(self, node: ast.CollectionLambda) -> Q:
Expand Down
64 changes: 46 additions & 18 deletions odata_query/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class ODataLexer(Lexer):
"ALL",
"WS",
}
literals = {"(", ")", ",", "/", ":"}
literals = {"(", ")", ",", "/", ":", "="}
reflags = re.I

# Ensure MyPy doesn't lose its mind:
Expand Down Expand Up @@ -563,23 +563,24 @@ def _function_call(self, func: ast.Identifier, args: List[ast._Node]):

func_name = func.full_name()

try:
n_args_exp = ODATA_FUNCTIONS[func_name]
except KeyError:
raise exceptions.UnknownFunctionException(func_name)

n_args_given = len(args)
if isinstance(n_args_exp, int) and n_args_given != n_args_exp:
raise exceptions.ArgumentCountException(
func_name, n_args_exp, n_args_exp, n_args_given
)

if isinstance(n_args_exp, tuple) and (
n_args_given < n_args_exp[0] or n_args_given > n_args_exp[1]
):
raise exceptions.ArgumentCountException(
func_name, n_args_exp[0], n_args_exp[1], n_args_given
)
if func.namespace in ((), ('geo',)):
try:
n_args_exp = ODATA_FUNCTIONS[func_name]
except KeyError:
raise exceptions.UnknownFunctionException(func_name)

n_args_given = len(args)
if isinstance(n_args_exp, int) and n_args_given != n_args_exp:
raise exceptions.ArgumentCountException(
func_name, n_args_exp, n_args_exp, n_args_given
)

if isinstance(n_args_exp, tuple) and (
n_args_given < n_args_exp[0] or n_args_given > n_args_exp[1]
):
raise exceptions.ArgumentCountException(
func_name, n_args_exp[0], n_args_exp[1], n_args_given
)

return ast.Call(func, args)

Expand All @@ -601,6 +602,33 @@ def common_expr(self, p):
args = p[1].val
return self._function_call(p[0], args)

@_('ODATA_IDENTIFIER "=" common_expr') # type:ignore[no-redef]
def named_param(self, p):
":meta private:"
return ast.NamedParam(p[0], p.common_expr)

@_('named_param BWS "," BWS named_param')
def list_named_param(self, p):
":meta private:"
return [p[0], p[4]]

@_('list_named_param BWS "," BWS named_param') # type:ignore[no-redef]
def list_named_param(self, p):
":meta private:"
return p.list_items + [p.named_param]

@_('ODATA_IDENTIFIER "(" BWS named_param BWS ")"') # type:ignore[no-redef]
def common_expr(self, p):
":meta private:"
args = [p.named_param]
return self._function_call(p[0], args)

@_('ODATA_IDENTIFIER "(" BWS list_named_param BWS ")"') # type:ignore[no-redef]
def common_expr(self, p):
":meta private:"
args = p.list_named_param
return self._function_call(p[0], args)

####################################################################################
# Misc
####################################################################################
Expand Down

0 comments on commit 7d62b9c

Please sign in to comment.