rule-engine icon indicating copy to clipboard operation
rule-engine copied to clipboard

Does rule engine support functions?

Open armona opened this issue 3 years ago • 5 comments

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"

armona avatar Jan 25 '22 19:01 armona

It's possible and I've been asked about it before. It would just be alot of work.

zeroSteiner avatar Jan 25 '22 20:01 zeroSteiner

I might have a poke at it on my spare times. Any guidance on how it should be implemented? which areas need change/additions?

armona avatar Jan 26 '22 14:01 armona

The ideal implementation would likely involve:

  1. A new function / callable datatype. New data types are a lot of work.
  2. Parser support for making a function call.
  3. 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.
  4. Type hinting and propagation as necessary for the AST node of the function call.
  5. Unit tests for the whole thing.
  6. Documentation updates describing how to use it.

zeroSteiner avatar Jan 26 '22 15:01 zeroSteiner

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 avatar Mar 20 '22 00:03 rwspielman

@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.

armona avatar Mar 20 '22 08:03 armona

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 avatar Jun 13 '23 08:06 nicolas-rdgs

@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 > 

zeroSteiner avatar Jun 13 '23 12:06 zeroSteiner

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.

zeroSteiner avatar Jun 17 '23 18:06 zeroSteiner

Progress is slow and steady. Will probably be done in another 3 weeks. Maybe less, maybe more.

zeroSteiner avatar Jul 01 '23 17:07 zeroSteiner

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 avatar Jul 15 '23 16:07 zeroSteiner

@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.

amrabed avatar Jul 26 '23 22:07 amrabed

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.

zeroSteiner avatar Jun 19 '24 19:06 zeroSteiner