Skip to content

Commit c8d2e4c

Browse files
Guillaume BernardGuillaume Bernard
Guillaume Bernard
and
Guillaume Bernard
authored
docs #35: use business objects as mappings (#36)
Closes #35 Co-authored-by: Guillaume Bernard <[email protected]>
1 parent b7b8938 commit c8d2e4c

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

docs/mkdocs.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ nav:
8585
- Advanced User Guide:
8686
- Parameters: parameters.md
8787
- Rule sets: rule_sets.md
88+
- Use your business objects: business_objects.md
8889
- Custom conditions: custom_conditions.md
8990
- API Reference: api_reference.md
9091
extra_css:

docs/pages/business_objects.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
You can use business objects (the instances of the classes from your business model) directly with the rule engine, without converting them to dictionaries.
2+
3+
**Arta** processes mappings in the rules and actions to inject data into functions, as you saw in the [simple example](a_simple_example.md) of this documentation.
4+
5+
## The problem with `dict` serialisation
6+
7+
Let’s consider the following example, representing a `Car` with its `Engine` which itself has attributes.
8+
9+
```python
10+
class Engine:
11+
power: int
12+
consumption: int
13+
14+
class Car:
15+
engine: Engine
16+
```
17+
18+
When following the regular usage of Arta, one would convert an instance of a `Car` to a dictionary with a sort of serializer. A candidate code for this might be:
19+
20+
```python
21+
from typing import Any
22+
23+
def serialize_car_to_dict(car: Car) -> dict[str, Any]:
24+
return {
25+
"engine": {
26+
"power": car.engine.power,
27+
"consumption": car.engine.consumption
28+
}
29+
}
30+
```
31+
32+
!!! tip "When using Pydantic"
33+
34+
If you use Pydantic, you might directly use the `model_dump` function in order to represent your object as a dictionnary object.
35+
36+
This way, you can write you conditions as follows:
37+
38+
```yaml
39+
conditions:
40+
HAS_LOWER_CONSUMPTION:
41+
description: "Whether an engine as a low consumption".
42+
validation_function: is_value_below_threshold
43+
condition_parameters:
44+
value: input.engine.power
45+
threshold: 10
46+
```
47+
48+
This is where the mapping is important: to go through the `engine` data and access the `power` attribute. We serialised the object in a custom code but there might be a better solution with less code…
49+
50+
## Transform any business object to a mapping
51+
52+
The solution to this issue is to use any business object as a mapping. In Python, any object may behave as such by subclassing [`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping) and implementing the abstract methods.
53+
54+
As an example, we provide the following mixin:
55+
56+
```python
57+
from collections.abc import Mapping, Iterator
58+
from typing import Any
59+
60+
class ObjectToMappingMixin(Mapping):
61+
def __iter__(self) -> Iterator[Any]:
62+
return iter(vars(self))
63+
64+
def __len__(self) -> int:
65+
return len(vars(self))
66+
67+
def __getitem__(self, key: str, /) -> Any:
68+
return getattr(self, key)
69+
```
70+
71+
Now, we can make our business objects subclass this mixin:
72+
73+
```python
74+
class Engine(ObjectToMappingMixin):
75+
power: int
76+
consumption: int
77+
78+
class Car(ObjectToMappingMixin):
79+
engine: Engine
80+
```
81+
82+
`Engine` and `Car` now behave as mapping and it’s possible to access the attributes of `Engine` from car using the dict’s `getitem` strategy, such as:
83+
84+
```python
85+
engine = Engine(power=1, consumption= 12)
86+
car = Car(engine=engine)
87+
88+
assert car["engine"]["power"] == 1
89+
assert car["engine"]["consumption"] == 12
90+
```
91+
92+
Finaly, when using the `RulesEngine.apply_rules` method, there is not longer need to convert your business objects to dictionaries, you can directly use them like:
93+
94+
```python
95+
from arta import RulesEngine
96+
97+
eng = RulesEngine(config_path="/to/my/config/dir")
98+
result = eng.apply_rules(input_data={"car": car})
99+
```

0 commit comments

Comments
 (0)