Skip to content

Commit

Permalink
Merge pull request #342 from caracal-pipeline/new-functions
Browse files Browse the repository at this point in the history
added IS_STR, IS_NUM, VALID to formula language
  • Loading branch information
o-smirnov authored Dec 11, 2024
2 parents 46803e6 + 8a5c77c commit a5b07a4
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
Expand Down
9 changes: 9 additions & 0 deletions docs/source/fundamentals/substitutions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ As we saw above, a parameter value starting with ``=`` invokes the formula parse

* ``STRIPEXT(`` *path* ``)`` returns the path minus the extension.

* ``IS_NUM(arg)`` true if the argument is a numeric type.

* ``IS_STR(arg)`` true if the argument is a string type.

* ``VALID(arg)`` true if the argument is valid, and evaluates to non-zero. This is a useful pattern when dealing
with parameters of a mixed type (that can be e.g. strings or numbers). For example, ``recipe.a > 0`` would throw an
error is ``a`` is a string, but ``VALID(recipe.a > 0)`` would return False in this case.


As should be evident from the list above, certain functions expect arguments of a particular type (for example, the pathname manipulation functions expect strings).

Note that function arguments are treated as fully-fledged expressions of their own (with the exception of the first argument of ``IFSET()``, which must be a namespace lookup by definition.) In particular, {}-substitutions are applied to string arguments. For example, the following can be a legit (and useful) invocation::
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ packages = [
]

[tool.poetry.dependencies]
python = "^3.8"
python = "^3.9"
numpy = "^1.20"
munch = "^2.5.0"
omegaconf = "^2.1"
importlib_metadata = { version = "4.13.0", python = "3.7" }
Expand Down
24 changes: 23 additions & 1 deletion scabha/evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os.path
import fnmatch
import pyparsing
import numpy as np
pyparsing.ParserElement.enable_packrat()
from pyparsing import *
from pyparsing import common
Expand Down Expand Up @@ -156,12 +157,33 @@ def RANGE(self, evaluator, args):
def make_range(*x):
return list(range(*x))
return self.evaluate_generic_callable(evaluator, "RANGE", make_range, args, min_args=1, max_args=3)

def VALID(self, evaluator, args):
if len(args) != 1:
raise FormulaError(f"{'.'.join(evaluator.location)}: VALID() expects one argument, got {len(args)}")
try:
result = evaluator._evaluate_result(args[0], allow_unset=True)
except (TypeError, ValueError) as exc:
return False
if type(result) is UNSET:
return False
return result

def MIN(self, evaluator, args):
return self.evaluate_generic_callable(evaluator, "MIN", min, args, min_args=1)

def MAX(self, evaluator, args):
return self.evaluate_generic_callable(evaluator, "MAX", max, args, min_args=1)

def IS_STR(self, evaluator, args):
def is_str(x):
return type(x) is str
return self.evaluate_generic_callable(evaluator, "IS_STR", is_str, args, min_args=1, max_args=1)

def IS_NUM(self, evaluator, args):
def is_num(x):
return np.isscalar(x) and (np.isreal(x) or np.isscalar(x))
return self.evaluate_generic_callable(evaluator, "IS_NUM", is_num, args, min_args=1, max_args=1)

def IF(self, evaluator, args):
if len(args) < 3 or len(args) > 4:
Expand Down Expand Up @@ -300,7 +322,7 @@ def construct_parser():

# functions
functions = reduce(operator.or_, map(Keyword, ["IF", "IFSET", "GLOB", "EXISTS", "LIST",
"BASENAME", "DIRNAME", "EXTENSION", "STRIPEXT", "MIN", "MAX", "RANGE", "NOSUBST", "SORT", "RSORT"]))
"BASENAME", "DIRNAME", "EXTENSION", "STRIPEXT", "MIN", "MAX", "IS_STR", "IS_NUM", "VALID", "RANGE", "NOSUBST", "SORT", "RSORT"]))
# these functions take one argument, which could also be a sequence
anyseq_functions = reduce(operator.or_, map(Keyword, ["GLOB", "EXISTS"]))

Expand Down

0 comments on commit a5b07a4

Please sign in to comment.