rule-engine
rule-engine copied to clipboard
Does rule engine support functions?
Was wondering if it's possible to implement custom functions to the rule engine, something of the sort of:
get_hostname("https://example.com/path") == "github.com"
It's possible and I've been asked about it before. It would just be alot of work.
I might have a poke at it on my spare times. Any guidance on how it should be implemented? which areas need change/additions?
The ideal implementation would likely involve:
- A new function / callable datatype. New data types are a lot of work.
- Parser support for making a function call.
- An AST node for the function call with a reduction method. The reduction should probably support the function being known at parse time which would involve a bit of research into solutions and patterns.
- Type hinting and propagation as necessary for the AST node of the function call.
- Unit tests for the whole thing.
- Documentation updates describing how to use it.
This may be something that I work on if it becomes necessary in my project (right now I can get around it, but this is a fairly important feature). @armona have you done any work on this?
@rwspielman I started working on it (not a lot though), but as @zeroSteiner said it is a lot of wok and I can't find the time to implement it.
I'm also interested about custom functions, I have an use case like this :
last_seen <= now('-15d')
Where last_seen
is a date in ISO format with timezone aware.
@nicolas-rdgs
You can do that already as long as last_seen
is a datetime.datetime
instance. DATETIME objects are timezone aware already. If there's no timezone information associated with it, then the default_timezone
is used from the context.
edit the 'context' and 'thing' objects as necessary
>>> import datetime
>>> thing = {'last_seen': datetime.datetime.now()}
>>>
exiting the edit console...
rule > last_seen <= ($now - t"P15D")
result:
False
rule >
I've started on this in a feature branch. There's still quite a bit left to do. I still need to:
- [x] Add support for type hinting
- [x] Test for and add necessary error handling
- Should account for arguments being the incorrect type
- Incorrect number of arguments
- Some kind of arbitrary Python exception
- [x] Add built functions
- [x] all
- [x] any
- [x] sum
- [x] map
- [x] max
- [x] min
- [x] filter
- [x] parse_datetime
- [x] parse_float
- [x] parse_timedelta
- [x] random
- [x] split
- [x] Add unit tests
- [x] Refactor and polish the code
- [x] Document the changes
- [x] Post the release
So far the parsing and AST node generation with reduction is in place and appears to be working just fine.
PYTHONPATH=lib python -m rule_engine.debug_repl --edit-console
edit the 'context' and 'thing' objects as necessary
>>> thing = {'noargs': lambda: 'hello!', 'add': lambda a,b: a + b, 'greet': lambda name: 'Hello ' + name}
>>>
exiting the edit console...
rule > noargs()
result:
'hello!'
rule > add(1, 2)
result:
Decimal('3.0')
rule > greet('Spencer')
result:
'Hello Spencer'
rule >
The branch is feat/functions
if anyone is interested in previewing it.
Progress is slow and steady. Will probably be done in another 3 weeks. Maybe less, maybe more.
This ticket has now been completed.
Functions are included in the latest release, version 4.0.0. This release included a few breaking changes that are noted in the change log. There are 12 builtin functions that are available out of the box. I included functions from all of the tickets I'd marked as duplicates of this request since this it was opened about a year a half ago (sum
, parse_datetime
, etc.). I've written documentation that describes the syntax and how function types can be defined.
@zeroSteiner it's not clear from the documentation how/where to define a new custom function when the rule is to be applied to an object. Would you please provide an example of such a case or point me to where I can find one?
Also, this part of the documentation about FUNCTION is a bit confusing:
Additional functions can be either added them to the evaluated object or by extending the builtin symbols. It is only possible to call a function from within the rule text. Functions can not be defined as other data types can be.
I clarified that a bit in the documentation. For anyone else that comes across this in the future, you add a function just like you'd add any other value.
thing = {'name': 'Test Case', 'my_function': my_function}
Rule('my_function(name)').evaluate(thing)
Alternatively, you can expose it through changing the builtin symbols and access it with the $
prefix.