https://github.com/raiderrobert/amino
A toolkit for custom rules engines
https://github.com/raiderrobert/amino
Last synced: over 1 year ago
JSON representation
A toolkit for custom rules engines
- Host: GitHub
- URL: https://github.com/raiderrobert/amino
- Owner: raiderrobert
- License: mit
- Created: 2021-07-12T12:43:13.000Z (about 5 years ago)
- Default Branch: main
- Last Pushed: 2024-01-04T19:30:43.000Z (over 2 years ago)
- Last Synced: 2024-05-01T13:42:12.943Z (about 2 years ago)
- Language: Python
- Homepage:
- Size: 73.2 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# amino
A toolkit and DSL for custom rules engines. Much focus has been given toward AI and machine learning tooling to help take humans out of the loop. However, there are exist a wide variety of current and future applications for custom rules engines.
We propose inverting the problem space and placing a schema at the center, not unlike how GraphQL has done so for APIs.
Amino has three parts:
- a schema definition like graphql or protobuf for the data space it operates on
- a pre-built small and extensible DSL for conditional logic to operate on these schemas
- a runtime to evaluate the rules against the data set
## How to Use
### Basic Example
Declare a schema
schema.amn
```
amount: int
state_code: str
```
Import schema and evaluate a rule to see if it matches the matching variables passed in.
```
>>> import amino
>>> amn = amino.load_schema("schema.amn")
>>> amn.eval("amount > 0 and state_code = 'CA'", {"amount": 100, "state_code": "CA"})
True
>>> amn.eval("amount > 0 and state_code = 'CA'", {"amount": 0, "state_code": "CA"})
False
```
### More likely Runtime Example
Declare a schema
schema.amn
```
amount: int
state_code: str
```
Import schema and use it in your code. ( Note: You don't need to specify `id` for just one data set or one rule, but
you do need an `id` for more than one of either, and each `id` must unique. )
```
>>> import amino
>>> amn = amino.load_schema("schema.amn")
>>> compiled = amn.compile(
... [
... {"id": 1, "rule":"amount > 0 and state_code = 'CA'"},
... {"id": 2, "rule":"amount > 10 and state_code = 'CA'"},
... {"id": 3, "rule":"amount >= 100"},
... ]
... )
>>> compiled.eval([
... {"id": 45, "amount": 100, "state_code": "CA"},
... {"id": 46, "amount": 50, "state_code": "CA"},
... {"id": 47, "amount": 100, "state_code": "NY"},
... {"id": 48, "amount": 10, "state_code": "NY"},
... ])
[
{"id": 45, "results": [1, 2, 3]},
{"id": 46, "results": [1, 2]},
{"id": 47, "results": [3]},
{"id": 48, "results": []},
]
```
We also support returning just one match.
```
>>> import amino
>>> amn = amino.load_schema("schema.amn")
>>> compiled = amn.compile(
... [
... {"id": 1, "rule":"amount > 0 and state_code = 'CA'", "ordering": 3},
... {"id": 2, "rule":"amount > 10 and state_code = 'CA'", "ordering": 2},
... {"id": 3, "rule":"amount >= 100", "ordering": 1},
... ],
... match={"option": "first", "key": "ordering", "ordering": "asc"}
... )
>>> compiled.eval([
... {"id": 100, "amount": 100, "state_code": "CA"},
... {"id": 101, "amount": 50, "state_code": "CA"},
... {"id": 102, "amount": 50, "state_code": "NY"},
... ])
[
{"id": 100, "results": [3]},
{"id": 101, "results": [2]},
{"id": 102, "results": []}
]
```
## Schema Elaboration
### Comments
We support comments with the `#` symbol. Anything to the right of the comment symbol is disregarded at runtime.
schema.amn
```
# this is a comment
amount: int # this is too
state_code: str
```
### Structs
We support C-like structs with the `struct` keyword
schema.amn
```
struct applicant {
state_code: str,
}
struct loan {
amount: int
}
```
```
>>> data = {"loan": {"amount": 100}, "applicant": "state_code": "CA"
>>> rule = "loan.amount > 0 and applicant.state_code = 'CA'"
>>> amn.eval(rule, data)
True
```
### Functions
We support function declarations; you declare the inputs and output, and you
implement the function in your own language. These aren't true functions. It may be more appropriate to call it a
foreign function interface declaration. That is, amino is the host language, and your implementation language in your
project (e.g. Python, TypeScript, etc.) is the guest language.
schema.amn
```
amount: int
state_code: str
smallest_number: (int, int) -> int
```
Note the passing of `min` and passing it into the `funcs` argument while loading the schema. This provides the DSL host language access to calling out to the guest function `min` while the host function in the DSL uses `smallest_number`.
```
>>> amn = amino.load_schema("schema.amn", funcs={'smallest_number': min})
>>> data = {"amount": 100, "state_code": "CA"}
>>> rule = "smallest_number(amount, 1000) < 1000 and state_code = 'CA'"
>>> amn.eval(rule, data)
True
```
#### Default Arguments
Functions also support more complex cases, such as referencing other variables in the schema:
schema.amn
```
COMPANY_MAX_LOAN_AMT: int = 100_000
loan_amount: int
approved_amount: int
state_code: str
within_tolerances: (COMPANY_MAX_LOAN_AMT)(loan_amount, approved_amount) -> bool
```
Note the passing of `within_tolerances` and passing it into the `funcs` argument while loading the schema.
At runtime, three variables in the order provided will be passed to `within_tolerances`
```
>>> import custom_module
>>> amn = amino.load_schema("schema.amn", {'within_tolerances': custom_module.within_tolerances})
>>> data = {"amount": 100, "state_code": "CA"}
>>> rule = "within_tolerances(10_000, 90_000) and state_code = 'CA'"
>>> amn.eval(rule, data)
True
```
### Lists
We support homogenous or heterogeneous arrays with the `list` keyword
schema.amn
```
state_code: str
amounts: list[int]
things: list[int|str|float]
```
```
>>> data = {"amount": 100, "state_code": "CA", "things": ["CA", 1, 1.0] }
>>> rule = "amount > 0 and state_code = 'CA' or state_code in things"
>>> amn.eval(rule, data)
True
```
## Operators
Built-in operators.
```
!=
=
>
<
>=
<=
in
not in
not
and
or
```