diff --git a/a_simple_example/index.html b/a_simple_example/index.html index 005cb34..3fdd5d1 100644 --- a/a_simple_example/index.html +++ b/a_simple_example/index.html @@ -914,12 +914,12 @@

Rules

simple_condition: input.power=="strength" or input.power=="fly" action: set_admission action_parameters: - value: True + value: true NOT_ADMITTED: simple_condition: null action: set_admission action_parameters: - value: False + value: false course: FRENCH: simple_condition: input.language=="french" and input.age!=None @@ -959,7 +959,7 @@

Actions

2 3
        action: set_admission  # (1)
         action_parameters:  # (2)
-          value: True  
+          value: true  
 
  1. Name of the python callable object used as an action function.
  2. diff --git a/search/search_index.json b/search/search_index.json index 674e3c6..567ef85 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"a_simple_example/","title":"A Simple Example","text":""},{"location":"a_simple_example/#intro","title":"Intro","text":"

    As we already mentioned: Arta is a simple python rules engine.

    But what do we mean by rules engine?

    "},{"location":"a_simple_example/#the-superhero-school","title":"The Superhero School","text":"

    Imagine the following use case:

    Your are managing a superhero school and you want to use some school rules in your python app.

    The rules (intentionally simple) are:

    Admission rules

    If the applicant has a school authorized power then he is admitted,

    Else he is not.

    Course selection rules

    If he is speaking french and his age is known then he must take the \"french\" course,

    Else if his age is unknown (e.g., it's a very old superhero), then he must take the \"senior\" course,

    Else if he is not speaking french, then he must take the \"international\" course.

    Send favorite meal rules

    If he is admitted and has a prefered dish, then we send an email to the school cook with the dish name.

    "},{"location":"a_simple_example/#rules","title":"Rules","text":"

    You can define above rules for Arta in one simple YAML file :

    ---\nrules:\n  default_rule_set:\n    admission:\n      ADMITTED:\n        simple_condition: input.power==\"strength\" or input.power==\"fly\"\n        action: set_admission\n        action_parameters:\n          value: True  \n      NOT_ADMITTED:\n        simple_condition: null\n        action: set_admission\n        action_parameters:\n          value: False\n    course:\n      FRENCH:\n        simple_condition: input.language==\"french\" and input.age!=None\n        action: set_course\n        action_parameters:\n          value: french\n      SENIOR:\n        simple_condition: input.age==None\n        action: set_course\n        action_parameters:\n          value: senior\n      INTERNATIONAL:\n        simple_condition: input.language!=\"french\"\n        action: set_course\n        action_parameters:\n          value: international\n    favorite_meal:\n      EMAIL:\n        simple_condition: input.favorite_meal!=None\n        action: send_email\n        action_parameters:\n          mail_to: cook@super-heroes.test\n          mail_content: \"Thanks for preparing once a month the following dish:\"\n          meal: input.favorite_meal\n\nactions_source_modules:\n  - my_folder.actions\n

    Simple Conditions

    This configuration uses what we called simple conditions, you can find out more here.

    "},{"location":"a_simple_example/#actions","title":"Actions","text":"

    An action is triggered when the conditions are verified (i.e., True).

    Actions are defined by the following keys in the previous YAML file:

            action: set_admission  # (1)\n        action_parameters:  # (2)\n          value: True  \n
    1. Name of the python callable object used as an action function.
    2. The action function arguments.

    The action function's implementation has to be located in the configured module:

    actions_source_modules:\n  - my_folder.actions\n

    And could be for example (intentionally simple) in actions.py:

    def set_admission(value: bool, **kwargs: Any) -> dict[str, bool]:\n    \"\"\"Return a dictionary containing the admission result.\"\"\"\n    return {\"is_admitted\": value}\n\n\ndef set_course(course_id: str, **kwargs: Any) -> dict[str, str]:\n    \"\"\"Return the course id as a dictionary.\"\"\"\n    return {\"course_id\": course_id}\n\n\ndef send_email(mail_to: str, mail_content: str, meal: str, **kwargs: Any) -> bool:\n    \"\"\"Send an email.\"\"\"\n    result: str | None = None\n\n    if meal is not None:\n        # API call here\n        result = \"sent\"\n\n    return result\n

    **kwargs

    **kwargs is mandatory in action functions.

    "},{"location":"a_simple_example/#engine","title":"Engine","text":"

    The rules engine is responsible for evaluating the configured rules against some data (usually named \"input data\").

    In our use case, the input data could be a list of applicants:

    applicants = [\n    {\n        \"id\": 1,\n        \"name\": \"Superman\",\n        \"civilian_name\": \"Clark Kent\",\n        \"age\": None,\n        \"city\": \"Metropolis\",\n        \"language\": \"english\",\n        \"power\": \"fly\",\n        \"favorite_meal\": \"Spinach\",\n        \"secret_weakness\": \"Kryptonite\",\n        \"weapons\": [],\n    },\n    {\n        \"id\": 2,\n        \"name\": \"Batman\",\n        \"civilian_name\": \"Bruce Wayne\",\n        \"age\": 33,\n        \"city\": \"Gotham City\",\n        \"language\": \"english\",\n        \"power\": \"strength\",\n        \"favorite_meal\": None,\n        \"secret_weakness\": \"Feel alone\",\n        \"weapons\": [\"Hands\", \"Batarang\"],\n    },\n    {\n        \"id\": 3,\n        \"name\": \"Wonder Woman\",\n        \"civilian_name\": \"Diana Prince\",\n        \"age\": 5000,\n        \"city\": \"Island of Themyscira\",\n        \"language\": \"french\",\n        \"power\": \"strength\",\n        \"favorite_meal\": None,\n        \"secret_weakness\": \"Lost faith in humanity\",\n        \"weapons\": [\"Magic lasso\", \"Bulletproof bracelets\", \"Sword\", \"Shield\"],\n    },\n]\n

    Now, let's apply the rules on a single applicant:

    from arta import RulesEngine\n\neng = RulesEngine(config_path=\"/to/my/config/dir\")  # (1)\n\nresult = eng.apply_rules(input_data=applicants[0])\n\nprint(result)  # (2)\n# {\n# \"admission\": {\"is_admitted\": True},\n# \"course\": {\"course_id\": \"senior\"},\n# \"favorite_meal\": \"sent\"\n# }\n
    1. Many possibilites for instanciation, we will explain them later.
    2. Print a single result for the first applicant.

    In the rules engine result, we have 3 outputs:

    Each corresponds to one of these rules.

    Here, we can apply the rules to all the data set (3 applicants) with a simple dictionary comprehension:

    from arta import RulesEngine\n\nresults = {applicant[\"name\"]: eng.apply_rules(applicant) for applicant in applicants}\n\nprint(results)\n# {\n#   \"Superman\": {\n#       \"admission\": {\"is_admitted\": True}, \n#       \"course\": {\"course_id\": \"senior\"}, \n#       \"favorite_meal\": \"sent\"},\n#   \"Batman\": {\n#       \"admission\": {\"is_admitted\": True},\n#       \"course\": {\"course_id\": \"international\"},\n#       \"favorite_meal\": None,\n#       },\n#   \"Wonder Woman\": {\n#       \"admission\": {\"is_admitted\": True},\n#       \"course\": {\"course_id\": \"french\"},\n#       \"favorite_meal\": None,\n#       }\n# }\n

    It is the end of this Arta's overview. If you want now to go deeper in how to use Arta, click here.

    "},{"location":"api_reference/","title":"API Reference","text":""},{"location":"api_reference/#_enginepy","title":"_engine.py","text":"

    Module implementing the rules engine.

    Class: RulesEngine

    "},{"location":"api_reference/#arta._engine.RulesEngine","title":"RulesEngine","text":"

    The Rules Engine is in charge of executing different groups of rules of a given rule set on user input data.

    Attributes:

    Name Type Description rules dict[str, dict[str, list[Rule]]]

    A dictionary of rules with k: rule set, v: (k: rule group, v: list of rule instances).

    Source code in arta/_engine.py
    class RulesEngine:\n    \"\"\"The Rules Engine is in charge of executing different groups of rules of a given rule set on user input data.\n\n    Attributes:\n        rules:  A dictionary of rules with k: rule set, v: (k: rule group, v: list of rule instances).\n    \"\"\"\n\n    # ==== Class constants ====\n\n    # Rule related config keys\n    CONST_RULE_SETS_CONF_KEY: str = \"rules\"\n    CONST_DFLT_RULE_SET_ID: str = \"default_rule_set\"\n    CONST_STD_RULE_CONDITION_CONF_KEY: str = \"condition\"\n    CONST_ACTION_CONF_KEY: str = \"action\"\n    CONST_ACTION_PARAMETERS_CONF_KEY: str = \"action_parameters\"\n\n    # Condition related config keys\n    CONST_STD_CONDITIONS_CONF_KEY: str = \"conditions\"\n    CONST_CONDITION_VALIDATION_FUNCTION_CONF_KEY: str = \"validation_function\"\n    CONST_CONDITION_DESCRIPTION_CONF_KEY: str = \"description\"\n    CONST_CONDITION_VALIDATION_PARAMETERS_CONF_KEY: str = \"condition_parameters\"\n    CONST_USER_CONDITION_STRING: str = \"USER_CONDITION\"\n\n    # Built-in factory mapping\n    BUILTIN_FACTORY_MAPPING: dict[str, type[BaseCondition]] = {\n        \"condition\": StandardCondition,\n        \"simple_condition\": SimpleCondition,\n    }\n\n    def __init__(\n        self,\n        *,\n        rules_dict: dict[str, dict[str, Any]] | None = None,\n        config_path: str | None = None,\n    ) -> None:\n        \"\"\"Initialize the rules.\n\n        2 possibilities: either 'rules_dict', or 'config_path', not both.\n\n        Args:\n            rules_dict: A dictionary containing the rules' definitions.\n            config_path: Path of a directory containing the YAML files.\n\n        Raises:\n            KeyError: Key not found.\n            TypeError: Wrong type.\n        \"\"\"\n        # Var init.\n        factory_mapping_classes: dict[str, type[BaseCondition]] = {}\n        std_condition_instances: dict[str, StandardCondition] = {}\n\n        if config_path is not None and rules_dict is not None:\n            raise ValueError(\"RulesEngine takes only one parameter: 'rules_dict' or 'config_path', not both.\")\n\n        # Init. default parsing_error_strategy (probably not needed because already defined elsewhere)\n        self._parsing_error_strategy: ParsingErrorStrategy = ParsingErrorStrategy.RAISE\n\n        # Initialize directly with a rules dict\n        if rules_dict is not None:\n            # Data validation\n            RulesDict.parse_obj(rules_dict)\n\n            # Edge cases data validation\n            if not isinstance(rules_dict, dict):\n                raise TypeError(f\"'rules_dict' must be dict type, not '{type(rules_dict)}'\")\n            elif len(rules_dict) == 0:\n                raise KeyError(\"'rules_dict' couldn't be empty.\")\n\n            # Attribute definition\n            self.rules: dict[str, dict[str, list[Rule]]] = self._adapt_user_rules_dict(rules_dict)\n\n        # Initialize with a config_path\n        elif config_path is not None:\n            # Load config in attribute\n            config_dict: dict[str, Any] = load_config(config_path)\n\n            # Data validation\n            config: Configuration = Configuration(**config_dict)\n\n            if config.parsing_error_strategy is not None:\n                # Set parsing error handling strategy from config\n                self._parsing_error_strategy = ParsingErrorStrategy(config.parsing_error_strategy)\n\n            # dict of available action functions (k: function name, v: function object)\n            action_modules: list[str] = config.actions_source_modules\n            action_functions: dict[str, Callable] = self._get_object_from_source_modules(action_modules)\n\n            # dict of available standard condition functions (k: function name, v: function object)\n            condition_modules: list[str] = (\n                config.conditions_source_modules if config.conditions_source_modules is not None else []\n            )\n            std_condition_functions: dict[str, Callable] = self._get_object_from_source_modules(condition_modules)\n\n            # Dictionary of condition instances (k: condition id, v: instance), built from config data\n            if len(std_condition_functions) > 0:\n                std_condition_instances = self._build_std_conditions(\n                    config=config.dict(), condition_functions_dict=std_condition_functions\n                )\n\n            # User-defined/custom conditions\n            if config.condition_factory_mapping is not None and config.custom_classes_source_modules is not None:\n                # dict of custom condition classes (k: classe name, v: class object)\n                custom_condition_classes: dict[str, type[BaseCondition]] = self._get_object_from_source_modules(\n                    config.custom_classes_source_modules\n                )\n\n                # Build a factory mapping dictionary (k: conf key, v:class object)\n                factory_mapping_classes.update(\n                    {\n                        conf_key: custom_condition_classes[class_name]\n                        for conf_key, class_name in config.condition_factory_mapping.items()\n                    }\n                )\n\n            # Arta built-in conditions\n            factory_mapping_classes.update(self.BUILTIN_FACTORY_MAPPING)\n\n            # Attribute definition\n            self.rules = self._build_rules(\n                std_condition_instances=std_condition_instances,\n                action_functions=action_functions,\n                config=config.dict(),\n                factory_mapping_classes=factory_mapping_classes,\n            )\n        else:\n            raise ValueError(\"RulesEngine needs a parameter: 'rule_dict' or 'config_path'.\")\n\n    def apply_rules(\n        self, input_data: dict[str, Any], *, rule_set: str | None = None, verbose: bool = False, **kwargs: Any\n    ) -> dict[str, Any]:\n        \"\"\"Apply the rules and return results.\n\n        For each rule group of a given rule set, rules are applied sequentially,\n        The loop is broken when a rule is applied (an action is triggered).\n        Then, the next rule group is evaluated.\n        And so on...\n\n        This means that the order of the rules in the configuration file\n        (e.g., rules.yaml) is meaningful.\n\n        Args:\n            input_data: Input data to apply rules on.\n            rule_set: Apply rules associated with the specified rule set.\n            verbose: If True, add extra ids (group_id, rule_id) for result explicability.\n            **kwargs: For user extra arguments.\n\n        Returns:\n            A dictionary containing the rule groups' results (k: group id, v: action result).\n\n        Raises:\n            TypeError: Wrong type.\n            KeyError: Key not found.\n        \"\"\"\n        # Input_data validation\n        if not isinstance(input_data, dict):\n            raise TypeError(f\"'input_data' must be dict type, not '{type(input_data)}'\")\n        elif len(input_data) == 0:\n            raise KeyError(\"'input_data' couldn't be empty.\")\n\n        # Var init.\n        input_data_copy: dict[str, Any] = copy.deepcopy(input_data)\n\n        # Prepare the result key\n        input_data_copy[\"output\"] = {}\n\n        # If there is no given rule set param. and there is only one rule set in self.rules\n        # and its value is 'default_rule_set', look for this one (rule_set='default_rule_set')\n        if rule_set is None and len(self.rules) == 1 and self.rules.get(self.CONST_DFLT_RULE_SET_ID) is not None:\n            rule_set = self.CONST_DFLT_RULE_SET_ID\n\n        # Check if given rule set is in self.rules?\n        if rule_set not in self.rules:\n            raise KeyError(\n                f\"Rule set '{rule_set}' not found in the rules, available rule sets are : {list(self.rules.keys())}.\"\n            )\n\n        # Var init.\n        results_dict: dict[str, Any] = {\"verbosity\": {\"rule_set\": rule_set, \"results\": []}}\n\n        # Groups' loop\n        for group_id, rules_list in self.rules[rule_set].items():\n            # Initialize result of the rule group with None\n            results_dict[group_id] = None\n\n            # Rules' loop (inside a group)\n            for rule in rules_list:\n                # Apply rules\n                action_result, rule_details = rule.apply(\n                    input_data_copy, parsing_error_strategy=self._parsing_error_strategy, **kwargs\n                )\n\n                # Check if the rule has been applied (= action activated)\n                if \"action_result\" in rule_details:\n                    # Save result and details\n                    results_dict[group_id] = action_result\n                    results_dict[\"verbosity\"][\"results\"].append(rule_details)\n\n                    # Update input data with current result with key 'output' (can be used in next rules)\n                    input_data_copy[\"output\"][group_id] = copy.deepcopy(results_dict[group_id])\n\n                    # We can only have one result per group => break when \"action_result\" in rule_details\n                    break\n\n        # Handling non-verbose mode\n        if not verbose:\n            results_dict.pop(\"verbosity\")\n\n        return results_dict\n\n    @staticmethod\n    def _get_object_from_source_modules(module_list: list[str]) -> dict[str, Any]:\n        \"\"\"(Protected)\n        Collect all functions defined in the list of modules.\n\n        Args:\n            module_list: List of source module names.\n\n        Returns:\n            Dictionary with objects found in the modules.\n        \"\"\"\n        object_dict: dict[str, Any] = {}\n\n        for module_name in module_list:\n            # Import module\n            mod: ModuleType = importlib.import_module(module_name)\n\n            # Collect functions\n            module_functions: dict[str, Any] = {key: val for key, val in getmembers(mod, isfunction)}\n            object_dict.update(module_functions)\n\n            # Collect classes\n            module_classes: dict[str, Any] = {key: val for key, val in getmembers(mod, isclass)}\n            object_dict.update(module_classes)\n\n        return object_dict\n\n    def _build_rules(\n        self,\n        std_condition_instances: dict[str, StandardCondition],\n        action_functions: dict[str, Callable],\n        config: dict[str, Any],\n        factory_mapping_classes: dict[str, type[BaseCondition]],\n    ) -> dict[str, dict[str, list[Any]]]:\n        \"\"\"(Protected)\n        Return a dictionary of Rule instances built from the configuration.\n\n        Args:\n            rule_sets: Sets of rules to be loaded in the Rules Engine (as needed by further uses).\n            std_condition_instances: Dictionary of condition instances (k: condition id, v: StandardCondition instance)\n            actions_dict: Dictionary of action functions (k: action name, v: Callable)\n            config: Dictionary of the imported configuration from yaml files.\n            factory_mapping_classes: A mapping dictionary (k: condition conf. key, v: custom class object)\n\n        Returns:\n            A dictionary of rules.\n        \"\"\"\n        # Var init.\n        rules_dict: dict[str, dict[str, list[Any]]] = {}\n\n        # Retrieve rule set ids from config\n        rule_set_ids: list[str] = list(config[self.CONST_RULE_SETS_CONF_KEY].keys())\n\n        # Going all way down to the rules (rule set > rule group > rule)\n        for set_id in rule_set_ids:\n            rules_conf: dict[str, Any] = config[self.CONST_RULE_SETS_CONF_KEY][set_id]\n            rules_dict[set_id] = {}\n            rule_set_dict: dict[str, list[Any]] = rules_dict[set_id]\n\n            # Looping throught groups\n            for group_id, group_rules in rules_conf.items():\n                # Initialize list or rules in the group\n                rule_set_dict[group_id] = []\n\n                # Looping through rules (inside a group)\n                for rule_id, rule_dict in group_rules.items():\n                    # Get action function\n                    action_function_name: str = rule_dict[self.CONST_ACTION_CONF_KEY]\n\n                    if action_function_name not in action_functions:\n                        raise KeyError(f\"Unknwown action function : {action_function_name}\")\n\n                    action: Callable = action_functions[action_function_name]\n\n                    # Look for condition conf. keys inside the rule\n                    condition_conf_keys: set[str] = set(rule_dict.keys()) - {\n                        self.CONST_ACTION_CONF_KEY,\n                        self.CONST_ACTION_PARAMETERS_CONF_KEY,\n                    }\n\n                    # Store the cond. expressions with the same order as in the configuration file (very important)\n                    condition_exprs: dict[str, str | None] = {\n                        key: value for key, value in rule_dict.items() if key in condition_conf_keys\n                    }\n\n                    # Create the corresponding Rule instance\n                    rule: Rule = Rule(\n                        set_id=set_id,\n                        group_id=group_id,\n                        rule_id=rule_id,\n                        action=action,\n                        action_parameters=rule_dict[self.CONST_ACTION_PARAMETERS_CONF_KEY],\n                        condition_exprs=condition_exprs,\n                        std_condition_instances=std_condition_instances,\n                        condition_factory_mapping=factory_mapping_classes,\n                    )\n                    rule_set_dict[group_id].append(rule)\n\n        return rules_dict\n\n    def _build_std_conditions(\n        self, config: dict[str, Any], condition_functions_dict: dict[str, Callable]\n    ) -> dict[str, StandardCondition]:\n        \"\"\"(Protected)\n        Return a dictionary of Condition instances built from the configuration file.\n\n        Args:\n            config: Dictionary of the imported configuration from yaml files.\n            condition_functions_dict: A dictionary where k:condition id, v:Callable (validation function).\n\n        Returns:\n            A dictionary of StandardCondition instances (k: condition id, v: StandardCondition instance).\n        \"\"\"\n        # Var init.\n        conditions_dict: dict[str, StandardCondition] = {}\n\n        # Condition configuration (under conditions' key)\n        conditions_conf: dict[str, dict[str, Any]] = config[self.CONST_STD_CONDITIONS_CONF_KEY]\n\n        # Looping through conditions (inside a group)\n        for condition_id, condition_params in conditions_conf.items():\n            # Get condition validation function\n            validation_function_name: str = condition_params[self.CONST_CONDITION_VALIDATION_FUNCTION_CONF_KEY]\n\n            if validation_function_name not in condition_functions_dict:\n                raise KeyError(f\"Unknwown validation function : {validation_function_name}\")\n\n            # Get Callable from function name\n            validation_function: Callable = condition_functions_dict[validation_function_name]\n\n            # Create Condition instance\n            condition_instance: StandardCondition = StandardCondition(\n                condition_id=condition_id,\n                description=condition_params[self.CONST_CONDITION_DESCRIPTION_CONF_KEY],\n                validation_function=validation_function,\n                validation_function_parameters=condition_params[self.CONST_CONDITION_VALIDATION_PARAMETERS_CONF_KEY],\n            )\n            conditions_dict[condition_id] = condition_instance\n\n        return conditions_dict\n\n    def _adapt_user_rules_dict(self, rules_dict: dict[str, dict[str, Any]]) -> dict[str, dict[str, list[Any]]]:\n        \"\"\"(Protected)\n        Return a dictionary of Rule's instances built from user's rules dictionary.\n\n        Args:\n            rules_dict: User raw rules dictionary.\n\n        Returns:\n            A rules dictionary made from the user input rules.\n        \"\"\"\n        # Var init.\n        rules_dict_formatted: dict[str, list[Any]] = {}\n\n        # Looping throught groups\n        for group_id, group_rules in rules_dict.items():\n            # Initialize list or rules in the group\n            rules_dict_formatted[group_id] = []\n\n            # Looping through rules (inside a group)\n            for rule_id, rule_dict in group_rules.items():\n                # Get action function\n                action = rule_dict[\"action\"]\n\n                # Trigger if not **kwargs\n                if \"kwargs\" not in inspect.signature(action).parameters:\n                    raise KeyError(f\"The action function {action} must have a '**kwargs' parameter.\")\n\n                # Create Rule instance\n                rule = Rule(\n                    set_id=self.CONST_DFLT_RULE_SET_ID,\n                    group_id=group_id,\n                    rule_id=rule_id,\n                    action=action,\n                    action_parameters=rule_dict.get(self.CONST_ACTION_PARAMETERS_CONF_KEY),\n                    condition_exprs={self.CONST_STD_RULE_CONDITION_CONF_KEY: self.CONST_USER_CONDITION_STRING}\n                    if self.CONST_STD_RULE_CONDITION_CONF_KEY in rule_dict\n                    and rule_dict.get(self.CONST_STD_RULE_CONDITION_CONF_KEY) is not None\n                    else {self.CONST_STD_RULE_CONDITION_CONF_KEY: None},\n                    std_condition_instances={\n                        self.CONST_USER_CONDITION_STRING: StandardCondition(\n                            condition_id=self.CONST_USER_CONDITION_STRING,\n                            description=\"Automatic description\",\n                            validation_function=rule_dict.get(self.CONST_STD_RULE_CONDITION_CONF_KEY),\n                            validation_function_parameters=rule_dict.get(\n                                self.CONST_CONDITION_VALIDATION_PARAMETERS_CONF_KEY\n                            ),\n                        )\n                    },\n                    condition_factory_mapping=self.BUILTIN_FACTORY_MAPPING,\n                )\n                rules_dict_formatted[group_id].append(rule)\n\n        return {self.CONST_DFLT_RULE_SET_ID: rules_dict_formatted}\n\n    def __str__(self) -> str:\n        \"\"\"Object human string representation (called by str()).\n\n        Returns:\n            A string representation of the instance.\n        \"\"\"\n        # Vars init.\n        attrs_str: str = \"\"\n\n        # Get some instance attributes infos\n        class_name: str = self.__class__.__name__\n        attrs: list[tuple[str, Any]] = [\n            attr\n            for attr in inspect.getmembers(self)\n            if not (\n                attr[0].startswith(\"_\")\n                or attr[0].startswith(\"CONST_\")\n                or isinstance(attr[1], (FunctionType, MethodType))\n            )\n        ]\n\n        # Build string representation\n        for attr, val in attrs:\n            attrs_str += f\"{attr}={str(val)}, \"\n\n        return f\"{class_name}({attrs_str})\"\n
    "},{"location":"api_reference/#arta._engine.RulesEngine.__init__","title":"__init__(*, rules_dict=None, config_path=None)","text":"

    Initialize the rules.

    2 possibilities: either 'rules_dict', or 'config_path', not both.

    Parameters:

    Name Type Description Default rules_dict dict[str, dict[str, Any]] | None

    A dictionary containing the rules' definitions.

    None config_path str | None

    Path of a directory containing the YAML files.

    None

    Raises:

    Type Description KeyError

    Key not found.

    TypeError

    Wrong type.

    Source code in arta/_engine.py
    def __init__(\n    self,\n    *,\n    rules_dict: dict[str, dict[str, Any]] | None = None,\n    config_path: str | None = None,\n) -> None:\n    \"\"\"Initialize the rules.\n\n    2 possibilities: either 'rules_dict', or 'config_path', not both.\n\n    Args:\n        rules_dict: A dictionary containing the rules' definitions.\n        config_path: Path of a directory containing the YAML files.\n\n    Raises:\n        KeyError: Key not found.\n        TypeError: Wrong type.\n    \"\"\"\n    # Var init.\n    factory_mapping_classes: dict[str, type[BaseCondition]] = {}\n    std_condition_instances: dict[str, StandardCondition] = {}\n\n    if config_path is not None and rules_dict is not None:\n        raise ValueError(\"RulesEngine takes only one parameter: 'rules_dict' or 'config_path', not both.\")\n\n    # Init. default parsing_error_strategy (probably not needed because already defined elsewhere)\n    self._parsing_error_strategy: ParsingErrorStrategy = ParsingErrorStrategy.RAISE\n\n    # Initialize directly with a rules dict\n    if rules_dict is not None:\n        # Data validation\n        RulesDict.parse_obj(rules_dict)\n\n        # Edge cases data validation\n        if not isinstance(rules_dict, dict):\n            raise TypeError(f\"'rules_dict' must be dict type, not '{type(rules_dict)}'\")\n        elif len(rules_dict) == 0:\n            raise KeyError(\"'rules_dict' couldn't be empty.\")\n\n        # Attribute definition\n        self.rules: dict[str, dict[str, list[Rule]]] = self._adapt_user_rules_dict(rules_dict)\n\n    # Initialize with a config_path\n    elif config_path is not None:\n        # Load config in attribute\n        config_dict: dict[str, Any] = load_config(config_path)\n\n        # Data validation\n        config: Configuration = Configuration(**config_dict)\n\n        if config.parsing_error_strategy is not None:\n            # Set parsing error handling strategy from config\n            self._parsing_error_strategy = ParsingErrorStrategy(config.parsing_error_strategy)\n\n        # dict of available action functions (k: function name, v: function object)\n        action_modules: list[str] = config.actions_source_modules\n        action_functions: dict[str, Callable] = self._get_object_from_source_modules(action_modules)\n\n        # dict of available standard condition functions (k: function name, v: function object)\n        condition_modules: list[str] = (\n            config.conditions_source_modules if config.conditions_source_modules is not None else []\n        )\n        std_condition_functions: dict[str, Callable] = self._get_object_from_source_modules(condition_modules)\n\n        # Dictionary of condition instances (k: condition id, v: instance), built from config data\n        if len(std_condition_functions) > 0:\n            std_condition_instances = self._build_std_conditions(\n                config=config.dict(), condition_functions_dict=std_condition_functions\n            )\n\n        # User-defined/custom conditions\n        if config.condition_factory_mapping is not None and config.custom_classes_source_modules is not None:\n            # dict of custom condition classes (k: classe name, v: class object)\n            custom_condition_classes: dict[str, type[BaseCondition]] = self._get_object_from_source_modules(\n                config.custom_classes_source_modules\n            )\n\n            # Build a factory mapping dictionary (k: conf key, v:class object)\n            factory_mapping_classes.update(\n                {\n                    conf_key: custom_condition_classes[class_name]\n                    for conf_key, class_name in config.condition_factory_mapping.items()\n                }\n            )\n\n        # Arta built-in conditions\n        factory_mapping_classes.update(self.BUILTIN_FACTORY_MAPPING)\n\n        # Attribute definition\n        self.rules = self._build_rules(\n            std_condition_instances=std_condition_instances,\n            action_functions=action_functions,\n            config=config.dict(),\n            factory_mapping_classes=factory_mapping_classes,\n        )\n    else:\n        raise ValueError(\"RulesEngine needs a parameter: 'rule_dict' or 'config_path'.\")\n
    "},{"location":"api_reference/#arta._engine.RulesEngine.__str__","title":"__str__()","text":"

    Object human string representation (called by str()).

    Returns:

    Type Description str

    A string representation of the instance.

    Source code in arta/_engine.py
    def __str__(self) -> str:\n    \"\"\"Object human string representation (called by str()).\n\n    Returns:\n        A string representation of the instance.\n    \"\"\"\n    # Vars init.\n    attrs_str: str = \"\"\n\n    # Get some instance attributes infos\n    class_name: str = self.__class__.__name__\n    attrs: list[tuple[str, Any]] = [\n        attr\n        for attr in inspect.getmembers(self)\n        if not (\n            attr[0].startswith(\"_\")\n            or attr[0].startswith(\"CONST_\")\n            or isinstance(attr[1], (FunctionType, MethodType))\n        )\n    ]\n\n    # Build string representation\n    for attr, val in attrs:\n        attrs_str += f\"{attr}={str(val)}, \"\n\n    return f\"{class_name}({attrs_str})\"\n
    "},{"location":"api_reference/#arta._engine.RulesEngine.apply_rules","title":"apply_rules(input_data, *, rule_set=None, verbose=False, **kwargs)","text":"

    Apply the rules and return results.

    For each rule group of a given rule set, rules are applied sequentially, The loop is broken when a rule is applied (an action is triggered). Then, the next rule group is evaluated. And so on...

    This means that the order of the rules in the configuration file (e.g., rules.yaml) is meaningful.

    Parameters:

    Name Type Description Default input_data dict[str, Any]

    Input data to apply rules on.

    required rule_set str | None

    Apply rules associated with the specified rule set.

    None verbose bool

    If True, add extra ids (group_id, rule_id) for result explicability.

    False **kwargs Any

    For user extra arguments.

    {}

    Returns:

    Type Description dict[str, Any]

    A dictionary containing the rule groups' results (k: group id, v: action result).

    Raises:

    Type Description TypeError

    Wrong type.

    KeyError

    Key not found.

    Source code in arta/_engine.py
    def apply_rules(\n    self, input_data: dict[str, Any], *, rule_set: str | None = None, verbose: bool = False, **kwargs: Any\n) -> dict[str, Any]:\n    \"\"\"Apply the rules and return results.\n\n    For each rule group of a given rule set, rules are applied sequentially,\n    The loop is broken when a rule is applied (an action is triggered).\n    Then, the next rule group is evaluated.\n    And so on...\n\n    This means that the order of the rules in the configuration file\n    (e.g., rules.yaml) is meaningful.\n\n    Args:\n        input_data: Input data to apply rules on.\n        rule_set: Apply rules associated with the specified rule set.\n        verbose: If True, add extra ids (group_id, rule_id) for result explicability.\n        **kwargs: For user extra arguments.\n\n    Returns:\n        A dictionary containing the rule groups' results (k: group id, v: action result).\n\n    Raises:\n        TypeError: Wrong type.\n        KeyError: Key not found.\n    \"\"\"\n    # Input_data validation\n    if not isinstance(input_data, dict):\n        raise TypeError(f\"'input_data' must be dict type, not '{type(input_data)}'\")\n    elif len(input_data) == 0:\n        raise KeyError(\"'input_data' couldn't be empty.\")\n\n    # Var init.\n    input_data_copy: dict[str, Any] = copy.deepcopy(input_data)\n\n    # Prepare the result key\n    input_data_copy[\"output\"] = {}\n\n    # If there is no given rule set param. and there is only one rule set in self.rules\n    # and its value is 'default_rule_set', look for this one (rule_set='default_rule_set')\n    if rule_set is None and len(self.rules) == 1 and self.rules.get(self.CONST_DFLT_RULE_SET_ID) is not None:\n        rule_set = self.CONST_DFLT_RULE_SET_ID\n\n    # Check if given rule set is in self.rules?\n    if rule_set not in self.rules:\n        raise KeyError(\n            f\"Rule set '{rule_set}' not found in the rules, available rule sets are : {list(self.rules.keys())}.\"\n        )\n\n    # Var init.\n    results_dict: dict[str, Any] = {\"verbosity\": {\"rule_set\": rule_set, \"results\": []}}\n\n    # Groups' loop\n    for group_id, rules_list in self.rules[rule_set].items():\n        # Initialize result of the rule group with None\n        results_dict[group_id] = None\n\n        # Rules' loop (inside a group)\n        for rule in rules_list:\n            # Apply rules\n            action_result, rule_details = rule.apply(\n                input_data_copy, parsing_error_strategy=self._parsing_error_strategy, **kwargs\n            )\n\n            # Check if the rule has been applied (= action activated)\n            if \"action_result\" in rule_details:\n                # Save result and details\n                results_dict[group_id] = action_result\n                results_dict[\"verbosity\"][\"results\"].append(rule_details)\n\n                # Update input data with current result with key 'output' (can be used in next rules)\n                input_data_copy[\"output\"][group_id] = copy.deepcopy(results_dict[group_id])\n\n                # We can only have one result per group => break when \"action_result\" in rule_details\n                break\n\n    # Handling non-verbose mode\n    if not verbose:\n        results_dict.pop(\"verbosity\")\n\n    return results_dict\n
    "},{"location":"api_reference/#conditionpy","title":"condition.py","text":"

    Condition implementation.

    Classes: BaseCondition, StandardCondition, SimpleCondition

    "},{"location":"api_reference/#arta.condition.BaseCondition","title":"BaseCondition","text":"

    Bases: ABC

    Base class of a Condition object (Strategy Pattern).

    Is an abstract class and can't be instantiated.

    Attributes:

    Name Type Description condition_id

    Id of a condition.

    description

    Description of a condition.

    validation_function

    Validation function of a condition.

    validation_function_parameters

    Arguments of the validation function.

    Source code in arta/condition.py
    class BaseCondition(ABC):\n    \"\"\"Base class of a Condition object (Strategy Pattern).\n\n    Is an abstract class and can't be instantiated.\n\n    Attributes:\n        condition_id: Id of a condition.\n        description: Description of a condition.\n        validation_function: Validation function of a condition.\n        validation_function_parameters: Arguments of the validation function.\n    \"\"\"\n\n    # Class constants\n    CONST_CONDITION_DATA_LABEL: str = \"Custom condition data (not needed)\"\n    CONDITION_ID_PATTERN: str = UPPERCASE_WORD_PATTERN\n\n    def __init__(\n        self,\n        condition_id: str,\n        description: str,\n        validation_function: Callable | None = None,\n        validation_function_parameters: dict[str, Any] | None = None,\n    ) -> None:\n        \"\"\"\n        Initialize attributes.\n\n        Args:\n            condition_id: Id of a condition.\n            description: Description of a condition.\n            validation_function: Validation function of a condition.\n            validation_function_parameters: Arguments of the validation function.\n        \"\"\"\n        self._condition_id = condition_id  # NOSONAR\n        self._description = description  # NOSONAR\n        self._validation_function = validation_function  # NOSONAR\n        self._validation_function_parameters = validation_function_parameters  # NOSONAR\n\n    @classmethod\n    def extract_condition_ids_from_expression(cls, condition_expr: str | None = None) -> set[str]:\n        \"\"\"Get the condition ids from a string (e.g., UPPERCASE words).\n\n        E.g., CONDITION_1 and not CONDITION_2\n\n        Warning: implementation is based on the current class constant CONDITION_SPLIT_PATTERN.\n\n        Args:\n            condition_expr: A boolean expression (string).\n\n        Returns:\n            A set of extracted condition ids.\n        \"\"\"\n        cond_ids: set[str] = set()\n\n        if condition_expr is not None:\n            cond_ids = set(re.findall(cls.CONDITION_ID_PATTERN, condition_expr))\n\n        return cond_ids\n\n    @abstractmethod\n    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n        \"\"\"(Abstract)\n        Return True if the condition is verified.\n\n        Args:\n            input_data: Input data to apply rules on.\n            parsing_error_strategy: Error handling strategy for parameter parsing.\n            **kwargs: For user extra arguments.\n\n        Returns:\n            True if the condition is verified, otherwise False.\n        \"\"\"\n        raise NotImplementedError\n
    "},{"location":"api_reference/#arta.condition.BaseCondition.__init__","title":"__init__(condition_id, description, validation_function=None, validation_function_parameters=None)","text":"

    Initialize attributes.

    Parameters:

    Name Type Description Default condition_id str

    Id of a condition.

    required description str

    Description of a condition.

    required validation_function Callable | None

    Validation function of a condition.

    None validation_function_parameters dict[str, Any] | None

    Arguments of the validation function.

    None Source code in arta/condition.py
    def __init__(\n    self,\n    condition_id: str,\n    description: str,\n    validation_function: Callable | None = None,\n    validation_function_parameters: dict[str, Any] | None = None,\n) -> None:\n    \"\"\"\n    Initialize attributes.\n\n    Args:\n        condition_id: Id of a condition.\n        description: Description of a condition.\n        validation_function: Validation function of a condition.\n        validation_function_parameters: Arguments of the validation function.\n    \"\"\"\n    self._condition_id = condition_id  # NOSONAR\n    self._description = description  # NOSONAR\n    self._validation_function = validation_function  # NOSONAR\n    self._validation_function_parameters = validation_function_parameters  # NOSONAR\n
    "},{"location":"api_reference/#arta.condition.BaseCondition.extract_condition_ids_from_expression","title":"extract_condition_ids_from_expression(condition_expr=None) classmethod","text":"

    Get the condition ids from a string (e.g., UPPERCASE words).

    E.g., CONDITION_1 and not CONDITION_2

    Warning: implementation is based on the current class constant CONDITION_SPLIT_PATTERN.

    Parameters:

    Name Type Description Default condition_expr str | None

    A boolean expression (string).

    None

    Returns:

    Type Description set[str]

    A set of extracted condition ids.

    Source code in arta/condition.py
    @classmethod\ndef extract_condition_ids_from_expression(cls, condition_expr: str | None = None) -> set[str]:\n    \"\"\"Get the condition ids from a string (e.g., UPPERCASE words).\n\n    E.g., CONDITION_1 and not CONDITION_2\n\n    Warning: implementation is based on the current class constant CONDITION_SPLIT_PATTERN.\n\n    Args:\n        condition_expr: A boolean expression (string).\n\n    Returns:\n        A set of extracted condition ids.\n    \"\"\"\n    cond_ids: set[str] = set()\n\n    if condition_expr is not None:\n        cond_ids = set(re.findall(cls.CONDITION_ID_PATTERN, condition_expr))\n\n    return cond_ids\n
    "},{"location":"api_reference/#arta.condition.BaseCondition.verify","title":"verify(input_data, parsing_error_strategy, **kwargs) abstractmethod","text":"

    (Abstract) Return True if the condition is verified.

    Parameters:

    Name Type Description Default input_data dict[str, Any]

    Input data to apply rules on.

    required parsing_error_strategy ParsingErrorStrategy

    Error handling strategy for parameter parsing.

    required **kwargs Any

    For user extra arguments.

    {}

    Returns:

    Type Description bool

    True if the condition is verified, otherwise False.

    Source code in arta/condition.py
    @abstractmethod\ndef verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n    \"\"\"(Abstract)\n    Return True if the condition is verified.\n\n    Args:\n        input_data: Input data to apply rules on.\n        parsing_error_strategy: Error handling strategy for parameter parsing.\n        **kwargs: For user extra arguments.\n\n    Returns:\n        True if the condition is verified, otherwise False.\n    \"\"\"\n    raise NotImplementedError\n
    "},{"location":"api_reference/#arta.condition.SimpleCondition","title":"SimpleCondition","text":"

    Bases: BaseCondition

    Class implementing a built-in simple condition.

    Attributes:

    Name Type Description condition_id

    Id of a condition.

    description

    Description of a condition.

    validation_function

    Validation function of a condition.

    validation_function_parameters

    Arguments of the validation function.

    Source code in arta/condition.py
    class SimpleCondition(BaseCondition):\n    \"\"\"Class implementing a built-in simple condition.\n\n    Attributes:\n        condition_id: Id of a condition.\n        description: Description of a condition.\n        validation_function: Validation function of a condition.\n        validation_function_parameters: Arguments of the validation function.\n    \"\"\"\n\n    # Class constants\n    CONST_CUSTOM_CONDITION_DATA_LABEL: str = \"Simple condition data (not needed)\"\n    CONDITION_ID_PATTERN: str = r\"(?:input\\.|output\\.)(?:[a-z_\\-0-9!=<>\\\"NTF\\.]*)\"\n\n    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n        \"\"\"Return True if the condition is verified.\n\n        Example of a unitary simple condition to be verified: 'input.age>=100'\n\n        Args:\n            input_data: Request or input data to apply rules on.\n            parsing_error_strategy: Error handling strategy for parameter parsing.\n            **kwargs: For user extra arguments.\n\n        Returns:\n            True if the condition is verified, otherwise False.\n\n        Raises:\n            AttributeError: Check the validation function or its parameters.\n        \"\"\"\n        bool_var: bool = False\n        unitary_expr: str = self._condition_id\n\n        data_path_patt: str = r\"(?:input\\.|output\\.)(?:[a-z_\\-\\.]*)\"\n\n        # Retrieve only the data path\n        path_matches: list[str] = re.findall(data_path_patt, unitary_expr)\n\n        if len(path_matches) == 1:\n            # Regular case: we have a data_path\n            data_path: str = path_matches[0]\n\n            # Read data from its path\n            data = parse_dynamic_parameter(  # noqa\n                parameter=data_path, input_data=input_data, parsing_error_strategy=parsing_error_strategy\n            )\n\n            # Replace with the variable name in the expression\n            eval_expr: str = unitary_expr.replace(data_path, \"data\")\n\n            # Evaluate the expression\n            try:\n                bool_var = eval(eval_expr)  # noqa\n            except TypeError:\n                # Ignore evaluation --> False\n                pass\n\n        elif parsing_error_strategy == ParsingErrorStrategy.RAISE:\n            # Raise an error because of no match for a data path\n            raise ConditionExecutionError(f\"Error when verifying simple condition: '{unitary_expr}'\")\n\n        else:\n            # Other case: ignore, default value => return False\n            pass\n\n        return bool_var\n
    "},{"location":"api_reference/#arta.condition.SimpleCondition.verify","title":"verify(input_data, parsing_error_strategy, **kwargs)","text":"

    Return True if the condition is verified.

    Example of a unitary simple condition to be verified: 'input.age>=100'

    Parameters:

    Name Type Description Default input_data dict[str, Any]

    Request or input data to apply rules on.

    required parsing_error_strategy ParsingErrorStrategy

    Error handling strategy for parameter parsing.

    required **kwargs Any

    For user extra arguments.

    {}

    Returns:

    Type Description bool

    True if the condition is verified, otherwise False.

    Raises:

    Type Description AttributeError

    Check the validation function or its parameters.

    Source code in arta/condition.py
    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n    \"\"\"Return True if the condition is verified.\n\n    Example of a unitary simple condition to be verified: 'input.age>=100'\n\n    Args:\n        input_data: Request or input data to apply rules on.\n        parsing_error_strategy: Error handling strategy for parameter parsing.\n        **kwargs: For user extra arguments.\n\n    Returns:\n        True if the condition is verified, otherwise False.\n\n    Raises:\n        AttributeError: Check the validation function or its parameters.\n    \"\"\"\n    bool_var: bool = False\n    unitary_expr: str = self._condition_id\n\n    data_path_patt: str = r\"(?:input\\.|output\\.)(?:[a-z_\\-\\.]*)\"\n\n    # Retrieve only the data path\n    path_matches: list[str] = re.findall(data_path_patt, unitary_expr)\n\n    if len(path_matches) == 1:\n        # Regular case: we have a data_path\n        data_path: str = path_matches[0]\n\n        # Read data from its path\n        data = parse_dynamic_parameter(  # noqa\n            parameter=data_path, input_data=input_data, parsing_error_strategy=parsing_error_strategy\n        )\n\n        # Replace with the variable name in the expression\n        eval_expr: str = unitary_expr.replace(data_path, \"data\")\n\n        # Evaluate the expression\n        try:\n            bool_var = eval(eval_expr)  # noqa\n        except TypeError:\n            # Ignore evaluation --> False\n            pass\n\n    elif parsing_error_strategy == ParsingErrorStrategy.RAISE:\n        # Raise an error because of no match for a data path\n        raise ConditionExecutionError(f\"Error when verifying simple condition: '{unitary_expr}'\")\n\n    else:\n        # Other case: ignore, default value => return False\n        pass\n\n    return bool_var\n
    "},{"location":"api_reference/#arta.condition.StandardCondition","title":"StandardCondition","text":"

    Bases: BaseCondition

    Class implementing a built-in condition, named standard condition.

    Attributes:

    Name Type Description condition_id

    Id of a condition.

    description

    Description of a condition.

    validation_function

    Validation function of a condition.

    validation_function_parameters

    Arguments of the validation function.

    Source code in arta/condition.py
    class StandardCondition(BaseCondition):\n    \"\"\"Class implementing a built-in condition, named standard condition.\n\n    Attributes:\n        condition_id: Id of a condition.\n        description: Description of a condition.\n        validation_function: Validation function of a condition.\n        validation_function_parameters: Arguments of the validation function.\n    \"\"\"\n\n    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n        \"\"\"Return True if the condition is verified.\n\n        Example of a unitary standard condition: CONDITION_1\n\n        Args:\n            input_data: Request or input data to apply rules on.\n            parsing_error_strategy: Error handling strategy for parameter parsing.\n            **kwargs: For user extra arguments.\n\n        Returns:\n            True if the condition is verified, otherwise False.\n\n        Raises:\n            AttributeError: Check the validation function or its parameters.\n        \"\"\"\n        if self._validation_function is None:\n            raise AttributeError(\"Validation function should not be None\")\n\n        if self._validation_function_parameters is None:\n            raise AttributeError(\"Validation function parameters should not be None\")\n\n        # Parse dynamic parameters\n        parameters: dict[str, Any] = {}\n\n        for key, value in self._validation_function_parameters.items():\n            parameters[key] = parse_dynamic_parameter(\n                parameter=value, input_data=input_data, parsing_error_strategy=parsing_error_strategy\n            )\n\n        # Run validation_function\n        return self._validation_function(**parameters)\n
    "},{"location":"api_reference/#arta.condition.StandardCondition.verify","title":"verify(input_data, parsing_error_strategy, **kwargs)","text":"

    Return True if the condition is verified.

    Example of a unitary standard condition: CONDITION_1

    Parameters:

    Name Type Description Default input_data dict[str, Any]

    Request or input data to apply rules on.

    required parsing_error_strategy ParsingErrorStrategy

    Error handling strategy for parameter parsing.

    required **kwargs Any

    For user extra arguments.

    {}

    Returns:

    Type Description bool

    True if the condition is verified, otherwise False.

    Raises:

    Type Description AttributeError

    Check the validation function or its parameters.

    Source code in arta/condition.py
    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n    \"\"\"Return True if the condition is verified.\n\n    Example of a unitary standard condition: CONDITION_1\n\n    Args:\n        input_data: Request or input data to apply rules on.\n        parsing_error_strategy: Error handling strategy for parameter parsing.\n        **kwargs: For user extra arguments.\n\n    Returns:\n        True if the condition is verified, otherwise False.\n\n    Raises:\n        AttributeError: Check the validation function or its parameters.\n    \"\"\"\n    if self._validation_function is None:\n        raise AttributeError(\"Validation function should not be None\")\n\n    if self._validation_function_parameters is None:\n        raise AttributeError(\"Validation function parameters should not be None\")\n\n    # Parse dynamic parameters\n    parameters: dict[str, Any] = {}\n\n    for key, value in self._validation_function_parameters.items():\n        parameters[key] = parse_dynamic_parameter(\n            parameter=value, input_data=input_data, parsing_error_strategy=parsing_error_strategy\n        )\n\n    # Run validation_function\n    return self._validation_function(**parameters)\n
    "},{"location":"glossary/","title":"Glossary","text":"Concept Definition action A task which is executed when conditions are verified. action function A callable object called to execute the action. action parameter Parameter of an action function. condition A condition to be verified before executing an action. condition id Identifier of a single condition (must be in CAPITAL LETTER). condition expression A boolean expression combining several conditions (meaning several condition id). condition function A callable object called to be verified therefore it returns a boolean. condition parameter Parameter of a condition/validation function. custom condition A user-defined condition. rule A set of conditions combined to one action. rule group A group of rules (usually sharing a common context). rule id Identifier of a single rule. rule set A set of rule groups (mostly one: default_rule_set). simple condition A built-in very simple condition. standard condition The regular built-in condition. validation function Same thing as a condition function."},{"location":"home/","title":"Home","text":"

    An Open Source Rules Engine - Make rule handling simple

    "},{"location":"home/#welcome-to-the-documentation","title":"Welcome to the documentation","text":"

    New feature

    Check out the new and very convenient feature called the simple condition. A new and lightweight way of configuring your rules' conditions.

    Arta is automatically tested with:

    Releases

    Want to see last updates, check the Release notes

    Pydantic 2

    Arta is now working with Pydantic 2! And of course, Pydantic 1 as well.

    "},{"location":"how_to/","title":"How to","text":"

    Ensure that you have correctly installed Arta before, check the Installation page

    "},{"location":"how_to/#simple-condition","title":"Simple condition","text":"

    Beta feature

    Simple condition is still a beta feature, some cases could not work as designed.

    Simple conditions are a new and straightforward way of configuring your conditions.

    It simplifies a lot your rules by:

    Note

    With the simple conditions you use straight boolean expressions directly in your configuration.

    It is easyer to read and maintain

    The configuration key here is:

    simple_condition:

    Example :

    ---\nrules:\n  default_rule_set:\n    admission:\n      ADM_OK:\n        simple_condition: input.power==\"strength\" or input.power==\"fly\"\n        action: set_admission\n        action_parameters:\n          value: OK \n      ADM_TO_BE_CHECKED:\n        simple_condition: input.age>=150 and input.age!=None\n        action: set_admission\n        action_parameters:\n          value: TO_CHECK     \n      ADM_KO:\n        simple_condition: null\n        action: set_admission\n        action_parameters:\n          value: KO\n\nactions_source_modules:\n  - my_folder.actions  # (1)\n
    1. Contains action function implementations, no need of the key conditions_source_modules here.

    How to write a simple condition like:

    input.power==\"strength\" or input.power==\"fly\"\n

    Warning

    Security concern

    Python code injection:

    Because Arta is using the eval() built-in function to evaluate simple conditions:

    "},{"location":"how_to/#standard-condition","title":"Standard condition","text":"

    It is the first implemented way of using Arta and probably the most powerful.

    The configuration key here is:

    condition:

    YAML

    The built-in file format used by Arta for configuration is YAML.

    Enhancement proposal

    We are thinking on a design that will allow user-implemented loading of the configuration (e.g., if you prefer using a JSON format). Stay tuned.

    "},{"location":"how_to/#yaml-file","title":"YAML file","text":"

    Simple Conditions

    The following YAML example illustrates how to configure usual standard conditions but there is another and simpler way to do it by using a special feature: the simple condition.

    Create a YAML file and define your rules like this:

    ---\nrules:\n  default_rule_set:  # (1)\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER  # (2)\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:  # (3)\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n\nconditions_source_modules:  # (4)\n  - my_folder.conditions\nactions_source_modules:  # (5)\n  - my_folder.actions\n
    1. This is the name of your rule set (i.e., default_rule_set is by default).
    2. You can't set a callable object here so we need to use a condition id.
    3. The conditions are identified by an id and defined here. The validation function is defined in a user's python module.
    4. This is the path of the module where the validation functions are implemented (you must change it).
    5. This is the path of the module where the action functions are implemented (you must change it).

    Warning

    Condition ids must be in capital letters here, it is mandatory (e.g., HAS_SCHOOL_AUTHORIZED_POWER).

    Tip

    You can split your configuration in multiple YAML files seamlessly in order to keep things clear. Example:

    It's very convenient when you have a lot of different rules and conditions in your app.

    "},{"location":"how_to/#condition-expression","title":"Condition expression","text":"

    In the above YAML, the following condition expression is intentionally very simple:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n

    The key condition: can take one condition id but also a condition expression (i.e., a boolean expression of condition ids) combining several conditions:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: (HAS_SCHOOL_AUTHORIZED_POWER or SPEAKS_FRENCH) and not(IS_EVIL)\n        action: set_admission\n        action_parameters:\n          value: true\n

    Warning

    In that example, you must define the 3 conditions in the configuration:

    Tip

    Use the condition expressions to keep things simple. Put your conditions in one expression as you can rather than creating several rules

    "},{"location":"how_to/#functions","title":"Functions","text":"

    We must create 2 modules:

    Note

    Module names are arbitrary, you can choose what you want.

    And implement our 2 needed validation and action functions (the one defined in the configuration file):

    conditions.py:

    def has_authorized_super_power(power):\n    return power in [\"strength\", \"fly\", \"immortality\"]\n

    actions.py:

    def set_admission(value, **kwargs):  # (1)\n    return {\"is_admitted\": value}\n
    1. **kwargs is mandatory here.

    Warning

    Function name and parameters must be the same as the one configured in the YAML file.

    "},{"location":"how_to/#usage","title":"Usage","text":"

    Once your configuration file and your functions are ready, you can use it very simply:

    from arta import RulesEngine\n\ninput_data = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"super_power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n\neng = RulesEngine(config_path=\"path/to/conf/dir\")\n\nresult = eng.apply_rules(input_data)\n\nprint(result)\n

    You should get:

    {'check_admission': {'is_admitted': True}}

    API Documentation

    You can get details on the RulesEngine parameters in the API Reference.

    "},{"location":"how_to/#concepts","title":"Concepts","text":"

    Let's go deeper into the concepts.

    "},{"location":"how_to/#rule-set-and-rule-group","title":"Rule set and rule group","text":"

    A rule set is composed of rule groups which are themselves composed of rules. We can find this tree structure in the following YAML:

    ---\nrules:\n  default_rule_set:  # (1)\n    check_admission:  # (2)\n      ADMITTED_RULE:  # (3)\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n
    1. This is the id of the rule set.
    2. This key define a rule group, we can have many groups (we have only one here for simplicity).
    3. This key is a rule id, which identifies rules among others.
    "},{"location":"how_to/#rule","title":"Rule","text":"

    Rule definitions are identified by an id (e.g., ADMITTED_RULE):

          ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n

    Tip

    Rule ids are in capital letters for readability only: it is an advised practice.

    Rules are made of 2 different things:

          ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n
          ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n
    "},{"location":"how_to/#condition-and-action","title":"Condition and Action","text":"

    Conditions and actions are quite similar in terms of implementation but their goals are different.

    Both are made of a callable object and some parameters:

    Parameter's special syntax

    The action and condition arguments can have a special syntax:

    condition_parameters:\n  power: input.super_power\n

    The string input.super_power is evaluated by the rules engine and it means \"fetch the key super_power in the input data\".

    "},{"location":"how_to/#dictionary-rules","title":"Dictionary rules","text":"

    Rules can be configured in a YAML file but they can also be defined by a regular dictionary:

    Without type hintsWith type hints (>=3.9)
    from arta import RulesEngine\n\nset_admission = lambda value, **kwargs: {\"is_admitted\": value}\n\nrules = {\n    \"check_admission\": {\n        \"ADMITTED_RULE\": {\n            \"condition\": lambda power: power in [\"strength\", \"fly\", \"immortality\"],\n            \"condition_parameters\": {\"power\": \"input.super_power\"}, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": True},\n        },\n        \"DEFAULT_RULE\": {\n            \"condition\": None,\n            \"condition_parameters\": None, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": False},\n        },\n    }\n}\n\ninput_data = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"super_power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n\neng = RulesEngine(rules_dict=rules)\n\nresult = eng.apply_rules(input_data)\n\nprint(result)\n
    from typing import Any, Callable\n\nfrom arta import RulesEngine\n\nset_admission: Callable = lambda value, **kwargs: {\"is_admitted\": value}\n\nrules: dict[str, Any] = {\n    \"check_admission\": {\n        \"ADMITTED_RULE\": {\n            \"condition\": lambda power: power in [\"strength\", \"fly\", \"immortality\"],\n            \"condition_parameters\": {\"power\": \"input.super_power\"}, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": True},\n        },\n        \"DEFAULT_RULE\": {\n            \"condition\": None,\n            \"condition_parameters\": None, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": False},\n        },\n    }\n}\n\ninput_data: dict[str, Any] = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"super_power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n\neng = RulesEngine(rules_dict=rules)\n\nresult: dict[str, Any] = eng.apply_rules(input_data)\n\nprint(result)\n

    You should get:

    {'check_admission': {'is_admitted': True}}\n

    Success

    Superman is admitted to the superhero school!

    Well done! By executing this code you have:

    1. Defined an action function (set_admission)
    2. Defined a rule set (rules)
    3. Used some input data (input_data)
    4. Instanciated a rules engine (RulesEngine)
    5. Applied the rules on the data and get some results (.apply_rules())

    Note

    In the code example we used some anonymous/lambda function for simplicity but it could be regular python functions as well.

    YAML vs Dictionary

    How to choose between dictionary and configuration?

    In most cases, you must choose the configuration way of defining your rules.

    You will improve your rules' maintainability a lot. In some cases like proof-of-concepts or Jupyter notebook works, you will probably be happy with straightforward dictionaries.

    Arta has plenty more features to discover. If you want to learn more, go to the next chapter: Advanced User Guide.

    "},{"location":"installation/","title":"Installation","text":""},{"location":"installation/#python","title":"Python","text":"

    Compatible with:

    "},{"location":"installation/#pip","title":"pip","text":"

    In your python environment:

    "},{"location":"installation/#regular-use","title":"Regular use","text":"
    pip install arta\n
    "},{"location":"installation/#development","title":"Development","text":"
    pip install arta[all]\n
    "},{"location":"parameters/","title":"Parameters","text":""},{"location":"parameters/#parsing-prefix-keywords","title":"Parsing prefix keywords","text":"

    There is 2 allowed parsing prefix keywords:

    Here are examples:

    1. input.name: maps to input_data[\"name\"].
    2. output.check_admission.is_admitted: maps to result[\"check_admission\"][\"is_admitted\"].

    They both can be used in condition and action parameters.

    Info

    A value without any prefix keyword is a constant.

    "},{"location":"parameters/#parsing-error","title":"Parsing error","text":""},{"location":"parameters/#raise-by-default","title":"Raise by default","text":"

    By default, errors during condition and action parameters parsing are raised.

    If we refer to the dictionary example:

    rules = {\n    \"check_admission\": {\n        \"ADMITTED_RULE\": {\n            \"condition\": lambda power: power in [\"strength\", \"fly\", \"immortality\"],\n            \"condition_parameters\": {\"power\": \"input.super_power\"}, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": True},\n        },\n        \"DEFAULT_RULE\": {\n            \"condition\": None,\n            \"condition_parameters\": None, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": False},\n        },\n    }\n}\n

    With modified data like:

    input_data = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n

    By default we will get a KeyError exception during the execution of the apply_rules() method because of power vs super_power.

    "},{"location":"parameters/#ignore","title":"Ignore","text":"

    You can change the by default raising behavior of the parameter's parsing.

    Two ways are possible:

    "},{"location":"parameters/#configuration-level","title":"Configuration level","text":"

    You just have to add the following key somewhere in your configuration:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules: \n  - my_folder.actions\n\nparsing_error_strategy: ignore  # (1)\n
    1. parsing_error_strategy has two possible values: raise and ignore.

    It will affect all the parameters.

    "},{"location":"parameters/#parameter-level","title":"Parameter level","text":"

    Quick Sum Up

    You can also handle more precisely that aspect at parameter's level:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power?  # (1)\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules: \n  - my_folder.actions\n
    1. Have you noticed the '?' ? If there is a KeyError when reading, power will be set to None rather than raising the exception.

    Info

    You can enforce raising exceptions at parameter's level with !.

    power: input.super_power!\n
    "},{"location":"parameters/#default-value-parameter-level","title":"Default value (parameter level)","text":"

    Finally, you can set a default value at parameter's level. This value will be used if there is an exception during parsing:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power?no_power  # (1)\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules: \n  - my_folder.actions\n
    1. If there is an exception during parsing, power will be set to \"no_power\".

    Good to know

    Parameter's level is overriding configuration level.

    "},{"location":"rule_sets/","title":"Rule sets","text":"

    Rule sets are a convenient way to separate your business rules into different collections.

    Doing so increases the rules' maintainability because of a better organization and fully uncoupled rules.

    Tip

    Rule sets are very usefull when you have a lot of rules.

    Info

    Most of the time, you won't need to handle different rule sets and will only use the default one: default_rule_set.

    The good news is that different rule sets can be used seamlessly with the same rules engine instance

    Let's take the following example:

    Based on that example, imagine that you need to add some rules about something totally different than the superhero school. Let's say rules for a dinosaur school.

    "},{"location":"rule_sets/#configuration","title":"Configuration","text":"

    Update your configuration by adding a new rule set: dinosaur_school_set

    ---\nrules:\n  superhero_school_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n  dinosaur_school_set:  # (1)\n    food_habit:\n      HERBIVOROUS:\n        condition: not(IS_EATING_MEAT)\n        action: send_mail_to_cook\n        action_parameters:\n          meal: \"plant\"\n      CARNIVOROUS:\n        condition: null\n        action: send_mail_to_cook\n        action_parameters:\n          meal: \"meat\"\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n  IS_EATING_MEAT:  # (2)\n    description: \"Is dinosaur eating meat?\"\n    validation_function: is_eating_meat\n    condition_parameters:\n      power: input.diet.regular_food\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules:\n  - my_folder.actions\n
    1. Add your new set of rules under the rules key
    2. Regular condition configuration, nothing new here

    Good to know

    You can define your rule sets into different YAML files (under the rules key in each).

    "},{"location":"rule_sets/#usage","title":"Usage","text":"

    Now that your rule sets are defined (and assuming that your condition and action functions are implemented in the right modules), you can easily use them:

    from arta import RulesEngine\n\ninput_data_1 = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"super_power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n\ninput_data_2 = {\n    \"id\": 1,\n    \"name\": \"Diplodocus\",\n    \"age\": 152000000,\n    \"length\": 31,\n    \"area\": \"north_america\",\n    \"diet\": {\n        \"regular_food\": \"plants\",\n    },\n}\n\neng = RulesEngine(config_path=\"path/to/conf/dir\")\n\nsuperhero_result = eng.apply_rules(input_data_1, rule_set=\"superhero_school_set\")  # (1)\n\ndinosaur_result = eng.apply_rules(input_data_2, rule_set=\"dinosaur_school_set\")\n
    1. Select the rule set that you want to use when applying rules on your input data.

    Good to know

    Input data can be different or the same among the rule sets. It depends on the use case.

    "},{"location":"rule_sets/#object-oriented-model","title":"Object-Oriented Model","text":"
    classDiagram\n    rule_set \"1\" -- \"1..*\" rule_group\n    rule_group \"1\" -- \"1..*\" rule\n    rule \"1..*\" -- \"0..*\" condition\n    rule \"1..*\" -- \"1\" action
    "},{"location":"special_conditions/","title":"Special conditions","text":""},{"location":"special_conditions/#custom-condition","title":"Custom condition","text":"

    Custom conditions are user-defined conditions.

    A custom condition will impact the atomic evaluation of each conditions (i.e., condition ids).

    Vocabulary

    To be more precise, a condition expression is something like:

    CONDITION_1 and CONDITION_2\n

    In that example, the condition expression is made of 2 conditions whose condition ids are:

    With the built-in condition (also named standard condition), condition ids map to validation functions and condition parameters but we can change that with a brand new custom condition.

    A custom condition example:

    my_condition: NAME_JOHN and AGE_42\n

    Remember

    condition ids have to be in CAPITAL LETTERS.

    Imagine you want it to be interpreted as (pseudo-code):

    if input.name == \"john\" and input.age == \"42\":\n    # Do something\n    ...\n

    With the custom conditions it's quite simple to implement.

    Why using a custom condition?

    The main goal is to simplify handling of recurrent conditions (e.i., \"recurrent\" meaning very similar conditions).

    "},{"location":"special_conditions/#class-implementation","title":"Class implementation","text":"

    First, create a class inheriting from BaseCondtion and implement the verify() method as you want/need:

    from typing import Any\n\nfrom arta.condition import BaseCondition\nfrom arta.utils import ParsingErrorStrategy\n\n\nclass MyCondition(BaseCondition):\n    def verify(\n        self,\n        input_data: dict[str, Any],\n        parsing_error_strategy: ParsingErrorStrategy,\n        **kwargs: Any\n    ) -> bool:\n\n        field, value = tuple(self.condition_id.split(\"_\"))\n\n        return input_data[field.lower()] == value.lower()\n

    self.condition_id

    self.condition_id will be NAME_JOHN for the first condition and AGE_42 for the second.

    Good to know

    The parsing_error_strategy can be used by the developer to adapt exception handling behavior. Possible values:

    ParsingErrorStrategy.RAISE\nParsingErrorStrategy.IGNORE\nParsingErrorStrategy.DEFAULT_VALUE\n
    "},{"location":"special_conditions/#configuration","title":"Configuration","text":"

    Last thing to do is to add your new custom condition in the configuration:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        my_condition: NAME_JOHN and AGE_42  # (1)\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules: \n  - my_folder.actions\n\ncustom_classes_source_modules:\n  - dir.to.my_module  # (2)\ncondition_factory_mapping:\n  my_condition: MyCondition # (3)\n
    1. Order is important, here it will evaluate condition then my_condition. Order is arbitrary.
    2. List of the modules containing custom classes
    3. Mapping between condition keys (my_condition) and custom classes (MyCondition)
    "},{"location":"special_conditions/#class-diagram","title":"Class diagram","text":"

    It is based on the following strategy pattern:

    classDiagram\n    note for MyCondition \"This is a custom condition class\"\n    RulesEngine \"1\" -- \"1..*\" Rule\n    Rule \"1..*\" -- \"0..*\" BaseCondition\n    BaseCondition <|-- StandardCondition\n    BaseCondition <|-- SimpleCondition\n    BaseCondition <|-- MyCondition\n    class RulesEngine{\n        +rules\n        +apply_rules()\n    }\n    class Rule {\n        #set_id\n        #group_id\n        #rule_id\n        #condition_exprs\n        #action\n        #action_parameters\n        +apply()\n    }\n    class BaseCondition {\n        <<abstract>>\n        #condition_id\n        #description\n        #validation_function\n        #validation_function_parameters\n        +verify()\n    }

    Good to know

    The class StandardCondition is the built-in implementation of a condition.

    "},{"location":"why/","title":"Why use Arta?","text":"

    There is one main reason for using Arta and it was the main goal of its development:

    Increase business rules maintainability.

    In other words, facilitate rules handling in a python app.

    "},{"location":"why/#before-arta","title":"Before Arta","text":"

    Rules in code can rapidly become a headache, kind of spaghetti dish of if, elif and else (or even match/case since Python 3.10).

    "},{"location":"why/#after-arta","title":"After Arta","text":"

    Arta increases rules maintainability:

    Improve collaboration

    Reading python code vs reading YAML.

    "},{"location":"assets/js/tarteaucitron/","title":"Index","text":""},{"location":"assets/js/tarteaucitron/#tarteaucitronjs","title":"tarteaucitron.js","text":"

    Comply to the european cookie law is simple with the french tarte au citron.

    "},{"location":"assets/js/tarteaucitron/#what-is-this-script","title":"What is this script?","text":"

    The european cookie law regulates the management of cookies and you should ask your visitors their consent before exposing them to third party services.

    Clearly this script will: - Disable all services by default, - Display a banner on the first page view and a small one on other pages, - Display a panel to allow or deny each services one by one, - Activate services on the second page view if not denied, - Store the consent in a cookie for 365 days.

    Bonus: - Load service when user click on Allow (without reload of the page), - Incorporate a fallback system (display a link instead of social button and a static banner instead of advertising).

    "},{"location":"assets/js/tarteaucitron/#supported-services","title":"Supported services","text":""},{"location":"assets/js/tarteaucitron/#visitors-outside-the-eu","title":"Visitors outside the EU","text":"

    In PHP for example, you can bypass all the script by setting this var tarteaucitron.user.bypass = true; if the visitor is not in the EU.

    "},{"location":"assets/js/tarteaucitron/#tested-on","title":"Tested on","text":""},{"location":"assets/js/tarteaucitron/#installation-guide","title":"Installation guide","text":"

    Visit opt-out.ferank.eu

    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"a_simple_example/","title":"A Simple Example","text":""},{"location":"a_simple_example/#intro","title":"Intro","text":"

    As we already mentioned: Arta is a simple python rules engine.

    But what do we mean by rules engine?

    "},{"location":"a_simple_example/#the-superhero-school","title":"The Superhero School","text":"

    Imagine the following use case:

    Your are managing a superhero school and you want to use some school rules in your python app.

    The rules (intentionally simple) are:

    Admission rules

    If the applicant has a school authorized power then he is admitted,

    Else he is not.

    Course selection rules

    If he is speaking french and his age is known then he must take the \"french\" course,

    Else if his age is unknown (e.g., it's a very old superhero), then he must take the \"senior\" course,

    Else if he is not speaking french, then he must take the \"international\" course.

    Send favorite meal rules

    If he is admitted and has a prefered dish, then we send an email to the school cook with the dish name.

    "},{"location":"a_simple_example/#rules","title":"Rules","text":"

    You can define above rules for Arta in one simple YAML file :

    ---\nrules:\n  default_rule_set:\n    admission:\n      ADMITTED:\n        simple_condition: input.power==\"strength\" or input.power==\"fly\"\n        action: set_admission\n        action_parameters:\n          value: true  \n      NOT_ADMITTED:\n        simple_condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n    course:\n      FRENCH:\n        simple_condition: input.language==\"french\" and input.age!=None\n        action: set_course\n        action_parameters:\n          value: french\n      SENIOR:\n        simple_condition: input.age==None\n        action: set_course\n        action_parameters:\n          value: senior\n      INTERNATIONAL:\n        simple_condition: input.language!=\"french\"\n        action: set_course\n        action_parameters:\n          value: international\n    favorite_meal:\n      EMAIL:\n        simple_condition: input.favorite_meal!=None\n        action: send_email\n        action_parameters:\n          mail_to: cook@super-heroes.test\n          mail_content: \"Thanks for preparing once a month the following dish:\"\n          meal: input.favorite_meal\n\nactions_source_modules:\n  - my_folder.actions\n

    Simple Conditions

    This configuration uses what we called simple conditions, you can find out more here.

    "},{"location":"a_simple_example/#actions","title":"Actions","text":"

    An action is triggered when the conditions are verified (i.e., True).

    Actions are defined by the following keys in the previous YAML file:

            action: set_admission  # (1)\n        action_parameters:  # (2)\n          value: true  \n
    1. Name of the python callable object used as an action function.
    2. The action function arguments.

    The action function's implementation has to be located in the configured module:

    actions_source_modules:\n  - my_folder.actions\n

    And could be for example (intentionally simple) in actions.py:

    def set_admission(value: bool, **kwargs: Any) -> dict[str, bool]:\n    \"\"\"Return a dictionary containing the admission result.\"\"\"\n    return {\"is_admitted\": value}\n\n\ndef set_course(course_id: str, **kwargs: Any) -> dict[str, str]:\n    \"\"\"Return the course id as a dictionary.\"\"\"\n    return {\"course_id\": course_id}\n\n\ndef send_email(mail_to: str, mail_content: str, meal: str, **kwargs: Any) -> bool:\n    \"\"\"Send an email.\"\"\"\n    result: str | None = None\n\n    if meal is not None:\n        # API call here\n        result = \"sent\"\n\n    return result\n

    **kwargs

    **kwargs is mandatory in action functions.

    "},{"location":"a_simple_example/#engine","title":"Engine","text":"

    The rules engine is responsible for evaluating the configured rules against some data (usually named \"input data\").

    In our use case, the input data could be a list of applicants:

    applicants = [\n    {\n        \"id\": 1,\n        \"name\": \"Superman\",\n        \"civilian_name\": \"Clark Kent\",\n        \"age\": None,\n        \"city\": \"Metropolis\",\n        \"language\": \"english\",\n        \"power\": \"fly\",\n        \"favorite_meal\": \"Spinach\",\n        \"secret_weakness\": \"Kryptonite\",\n        \"weapons\": [],\n    },\n    {\n        \"id\": 2,\n        \"name\": \"Batman\",\n        \"civilian_name\": \"Bruce Wayne\",\n        \"age\": 33,\n        \"city\": \"Gotham City\",\n        \"language\": \"english\",\n        \"power\": \"strength\",\n        \"favorite_meal\": None,\n        \"secret_weakness\": \"Feel alone\",\n        \"weapons\": [\"Hands\", \"Batarang\"],\n    },\n    {\n        \"id\": 3,\n        \"name\": \"Wonder Woman\",\n        \"civilian_name\": \"Diana Prince\",\n        \"age\": 5000,\n        \"city\": \"Island of Themyscira\",\n        \"language\": \"french\",\n        \"power\": \"strength\",\n        \"favorite_meal\": None,\n        \"secret_weakness\": \"Lost faith in humanity\",\n        \"weapons\": [\"Magic lasso\", \"Bulletproof bracelets\", \"Sword\", \"Shield\"],\n    },\n]\n

    Now, let's apply the rules on a single applicant:

    from arta import RulesEngine\n\neng = RulesEngine(config_path=\"/to/my/config/dir\")  # (1)\n\nresult = eng.apply_rules(input_data=applicants[0])\n\nprint(result)  # (2)\n# {\n# \"admission\": {\"is_admitted\": True},\n# \"course\": {\"course_id\": \"senior\"},\n# \"favorite_meal\": \"sent\"\n# }\n
    1. Many possibilites for instanciation, we will explain them later.
    2. Print a single result for the first applicant.

    In the rules engine result, we have 3 outputs:

    Each corresponds to one of these rules.

    Here, we can apply the rules to all the data set (3 applicants) with a simple dictionary comprehension:

    from arta import RulesEngine\n\nresults = {applicant[\"name\"]: eng.apply_rules(applicant) for applicant in applicants}\n\nprint(results)\n# {\n#   \"Superman\": {\n#       \"admission\": {\"is_admitted\": True}, \n#       \"course\": {\"course_id\": \"senior\"}, \n#       \"favorite_meal\": \"sent\"},\n#   \"Batman\": {\n#       \"admission\": {\"is_admitted\": True},\n#       \"course\": {\"course_id\": \"international\"},\n#       \"favorite_meal\": None,\n#       },\n#   \"Wonder Woman\": {\n#       \"admission\": {\"is_admitted\": True},\n#       \"course\": {\"course_id\": \"french\"},\n#       \"favorite_meal\": None,\n#       }\n# }\n

    It is the end of this Arta's overview. If you want now to go deeper in how to use Arta, click here.

    "},{"location":"api_reference/","title":"API Reference","text":""},{"location":"api_reference/#_enginepy","title":"_engine.py","text":"

    Module implementing the rules engine.

    Class: RulesEngine

    "},{"location":"api_reference/#arta._engine.RulesEngine","title":"RulesEngine","text":"

    The Rules Engine is in charge of executing different groups of rules of a given rule set on user input data.

    Attributes:

    Name Type Description rules dict[str, dict[str, list[Rule]]]

    A dictionary of rules with k: rule set, v: (k: rule group, v: list of rule instances).

    Source code in arta/_engine.py
    class RulesEngine:\n    \"\"\"The Rules Engine is in charge of executing different groups of rules of a given rule set on user input data.\n\n    Attributes:\n        rules:  A dictionary of rules with k: rule set, v: (k: rule group, v: list of rule instances).\n    \"\"\"\n\n    # ==== Class constants ====\n\n    # Rule related config keys\n    CONST_RULE_SETS_CONF_KEY: str = \"rules\"\n    CONST_DFLT_RULE_SET_ID: str = \"default_rule_set\"\n    CONST_STD_RULE_CONDITION_CONF_KEY: str = \"condition\"\n    CONST_ACTION_CONF_KEY: str = \"action\"\n    CONST_ACTION_PARAMETERS_CONF_KEY: str = \"action_parameters\"\n\n    # Condition related config keys\n    CONST_STD_CONDITIONS_CONF_KEY: str = \"conditions\"\n    CONST_CONDITION_VALIDATION_FUNCTION_CONF_KEY: str = \"validation_function\"\n    CONST_CONDITION_DESCRIPTION_CONF_KEY: str = \"description\"\n    CONST_CONDITION_VALIDATION_PARAMETERS_CONF_KEY: str = \"condition_parameters\"\n    CONST_USER_CONDITION_STRING: str = \"USER_CONDITION\"\n\n    # Built-in factory mapping\n    BUILTIN_FACTORY_MAPPING: dict[str, type[BaseCondition]] = {\n        \"condition\": StandardCondition,\n        \"simple_condition\": SimpleCondition,\n    }\n\n    def __init__(\n        self,\n        *,\n        rules_dict: dict[str, dict[str, Any]] | None = None,\n        config_path: str | None = None,\n    ) -> None:\n        \"\"\"Initialize the rules.\n\n        2 possibilities: either 'rules_dict', or 'config_path', not both.\n\n        Args:\n            rules_dict: A dictionary containing the rules' definitions.\n            config_path: Path of a directory containing the YAML files.\n\n        Raises:\n            KeyError: Key not found.\n            TypeError: Wrong type.\n        \"\"\"\n        # Var init.\n        factory_mapping_classes: dict[str, type[BaseCondition]] = {}\n        std_condition_instances: dict[str, StandardCondition] = {}\n\n        if config_path is not None and rules_dict is not None:\n            raise ValueError(\"RulesEngine takes only one parameter: 'rules_dict' or 'config_path', not both.\")\n\n        # Init. default parsing_error_strategy (probably not needed because already defined elsewhere)\n        self._parsing_error_strategy: ParsingErrorStrategy = ParsingErrorStrategy.RAISE\n\n        # Initialize directly with a rules dict\n        if rules_dict is not None:\n            # Data validation\n            RulesDict.parse_obj(rules_dict)\n\n            # Edge cases data validation\n            if not isinstance(rules_dict, dict):\n                raise TypeError(f\"'rules_dict' must be dict type, not '{type(rules_dict)}'\")\n            elif len(rules_dict) == 0:\n                raise KeyError(\"'rules_dict' couldn't be empty.\")\n\n            # Attribute definition\n            self.rules: dict[str, dict[str, list[Rule]]] = self._adapt_user_rules_dict(rules_dict)\n\n        # Initialize with a config_path\n        elif config_path is not None:\n            # Load config in attribute\n            config_dict: dict[str, Any] = load_config(config_path)\n\n            # Data validation\n            config: Configuration = Configuration(**config_dict)\n\n            if config.parsing_error_strategy is not None:\n                # Set parsing error handling strategy from config\n                self._parsing_error_strategy = ParsingErrorStrategy(config.parsing_error_strategy)\n\n            # dict of available action functions (k: function name, v: function object)\n            action_modules: list[str] = config.actions_source_modules\n            action_functions: dict[str, Callable] = self._get_object_from_source_modules(action_modules)\n\n            # dict of available standard condition functions (k: function name, v: function object)\n            condition_modules: list[str] = (\n                config.conditions_source_modules if config.conditions_source_modules is not None else []\n            )\n            std_condition_functions: dict[str, Callable] = self._get_object_from_source_modules(condition_modules)\n\n            # Dictionary of condition instances (k: condition id, v: instance), built from config data\n            if len(std_condition_functions) > 0:\n                std_condition_instances = self._build_std_conditions(\n                    config=config.dict(), condition_functions_dict=std_condition_functions\n                )\n\n            # User-defined/custom conditions\n            if config.condition_factory_mapping is not None and config.custom_classes_source_modules is not None:\n                # dict of custom condition classes (k: classe name, v: class object)\n                custom_condition_classes: dict[str, type[BaseCondition]] = self._get_object_from_source_modules(\n                    config.custom_classes_source_modules\n                )\n\n                # Build a factory mapping dictionary (k: conf key, v:class object)\n                factory_mapping_classes.update(\n                    {\n                        conf_key: custom_condition_classes[class_name]\n                        for conf_key, class_name in config.condition_factory_mapping.items()\n                    }\n                )\n\n            # Arta built-in conditions\n            factory_mapping_classes.update(self.BUILTIN_FACTORY_MAPPING)\n\n            # Attribute definition\n            self.rules = self._build_rules(\n                std_condition_instances=std_condition_instances,\n                action_functions=action_functions,\n                config=config.dict(),\n                factory_mapping_classes=factory_mapping_classes,\n            )\n        else:\n            raise ValueError(\"RulesEngine needs a parameter: 'rule_dict' or 'config_path'.\")\n\n    def apply_rules(\n        self, input_data: dict[str, Any], *, rule_set: str | None = None, verbose: bool = False, **kwargs: Any\n    ) -> dict[str, Any]:\n        \"\"\"Apply the rules and return results.\n\n        For each rule group of a given rule set, rules are applied sequentially,\n        The loop is broken when a rule is applied (an action is triggered).\n        Then, the next rule group is evaluated.\n        And so on...\n\n        This means that the order of the rules in the configuration file\n        (e.g., rules.yaml) is meaningful.\n\n        Args:\n            input_data: Input data to apply rules on.\n            rule_set: Apply rules associated with the specified rule set.\n            verbose: If True, add extra ids (group_id, rule_id) for result explicability.\n            **kwargs: For user extra arguments.\n\n        Returns:\n            A dictionary containing the rule groups' results (k: group id, v: action result).\n\n        Raises:\n            TypeError: Wrong type.\n            KeyError: Key not found.\n        \"\"\"\n        # Input_data validation\n        if not isinstance(input_data, dict):\n            raise TypeError(f\"'input_data' must be dict type, not '{type(input_data)}'\")\n        elif len(input_data) == 0:\n            raise KeyError(\"'input_data' couldn't be empty.\")\n\n        # Var init.\n        input_data_copy: dict[str, Any] = copy.deepcopy(input_data)\n\n        # Prepare the result key\n        input_data_copy[\"output\"] = {}\n\n        # If there is no given rule set param. and there is only one rule set in self.rules\n        # and its value is 'default_rule_set', look for this one (rule_set='default_rule_set')\n        if rule_set is None and len(self.rules) == 1 and self.rules.get(self.CONST_DFLT_RULE_SET_ID) is not None:\n            rule_set = self.CONST_DFLT_RULE_SET_ID\n\n        # Check if given rule set is in self.rules?\n        if rule_set not in self.rules:\n            raise KeyError(\n                f\"Rule set '{rule_set}' not found in the rules, available rule sets are : {list(self.rules.keys())}.\"\n            )\n\n        # Var init.\n        results_dict: dict[str, Any] = {\"verbosity\": {\"rule_set\": rule_set, \"results\": []}}\n\n        # Groups' loop\n        for group_id, rules_list in self.rules[rule_set].items():\n            # Initialize result of the rule group with None\n            results_dict[group_id] = None\n\n            # Rules' loop (inside a group)\n            for rule in rules_list:\n                # Apply rules\n                action_result, rule_details = rule.apply(\n                    input_data_copy, parsing_error_strategy=self._parsing_error_strategy, **kwargs\n                )\n\n                # Check if the rule has been applied (= action activated)\n                if \"action_result\" in rule_details:\n                    # Save result and details\n                    results_dict[group_id] = action_result\n                    results_dict[\"verbosity\"][\"results\"].append(rule_details)\n\n                    # Update input data with current result with key 'output' (can be used in next rules)\n                    input_data_copy[\"output\"][group_id] = copy.deepcopy(results_dict[group_id])\n\n                    # We can only have one result per group => break when \"action_result\" in rule_details\n                    break\n\n        # Handling non-verbose mode\n        if not verbose:\n            results_dict.pop(\"verbosity\")\n\n        return results_dict\n\n    @staticmethod\n    def _get_object_from_source_modules(module_list: list[str]) -> dict[str, Any]:\n        \"\"\"(Protected)\n        Collect all functions defined in the list of modules.\n\n        Args:\n            module_list: List of source module names.\n\n        Returns:\n            Dictionary with objects found in the modules.\n        \"\"\"\n        object_dict: dict[str, Any] = {}\n\n        for module_name in module_list:\n            # Import module\n            mod: ModuleType = importlib.import_module(module_name)\n\n            # Collect functions\n            module_functions: dict[str, Any] = {key: val for key, val in getmembers(mod, isfunction)}\n            object_dict.update(module_functions)\n\n            # Collect classes\n            module_classes: dict[str, Any] = {key: val for key, val in getmembers(mod, isclass)}\n            object_dict.update(module_classes)\n\n        return object_dict\n\n    def _build_rules(\n        self,\n        std_condition_instances: dict[str, StandardCondition],\n        action_functions: dict[str, Callable],\n        config: dict[str, Any],\n        factory_mapping_classes: dict[str, type[BaseCondition]],\n    ) -> dict[str, dict[str, list[Any]]]:\n        \"\"\"(Protected)\n        Return a dictionary of Rule instances built from the configuration.\n\n        Args:\n            rule_sets: Sets of rules to be loaded in the Rules Engine (as needed by further uses).\n            std_condition_instances: Dictionary of condition instances (k: condition id, v: StandardCondition instance)\n            actions_dict: Dictionary of action functions (k: action name, v: Callable)\n            config: Dictionary of the imported configuration from yaml files.\n            factory_mapping_classes: A mapping dictionary (k: condition conf. key, v: custom class object)\n\n        Returns:\n            A dictionary of rules.\n        \"\"\"\n        # Var init.\n        rules_dict: dict[str, dict[str, list[Any]]] = {}\n\n        # Retrieve rule set ids from config\n        rule_set_ids: list[str] = list(config[self.CONST_RULE_SETS_CONF_KEY].keys())\n\n        # Going all way down to the rules (rule set > rule group > rule)\n        for set_id in rule_set_ids:\n            rules_conf: dict[str, Any] = config[self.CONST_RULE_SETS_CONF_KEY][set_id]\n            rules_dict[set_id] = {}\n            rule_set_dict: dict[str, list[Any]] = rules_dict[set_id]\n\n            # Looping throught groups\n            for group_id, group_rules in rules_conf.items():\n                # Initialize list or rules in the group\n                rule_set_dict[group_id] = []\n\n                # Looping through rules (inside a group)\n                for rule_id, rule_dict in group_rules.items():\n                    # Get action function\n                    action_function_name: str = rule_dict[self.CONST_ACTION_CONF_KEY]\n\n                    if action_function_name not in action_functions:\n                        raise KeyError(f\"Unknwown action function : {action_function_name}\")\n\n                    action: Callable = action_functions[action_function_name]\n\n                    # Look for condition conf. keys inside the rule\n                    condition_conf_keys: set[str] = set(rule_dict.keys()) - {\n                        self.CONST_ACTION_CONF_KEY,\n                        self.CONST_ACTION_PARAMETERS_CONF_KEY,\n                    }\n\n                    # Store the cond. expressions with the same order as in the configuration file (very important)\n                    condition_exprs: dict[str, str | None] = {\n                        key: value for key, value in rule_dict.items() if key in condition_conf_keys\n                    }\n\n                    # Create the corresponding Rule instance\n                    rule: Rule = Rule(\n                        set_id=set_id,\n                        group_id=group_id,\n                        rule_id=rule_id,\n                        action=action,\n                        action_parameters=rule_dict[self.CONST_ACTION_PARAMETERS_CONF_KEY],\n                        condition_exprs=condition_exprs,\n                        std_condition_instances=std_condition_instances,\n                        condition_factory_mapping=factory_mapping_classes,\n                    )\n                    rule_set_dict[group_id].append(rule)\n\n        return rules_dict\n\n    def _build_std_conditions(\n        self, config: dict[str, Any], condition_functions_dict: dict[str, Callable]\n    ) -> dict[str, StandardCondition]:\n        \"\"\"(Protected)\n        Return a dictionary of Condition instances built from the configuration file.\n\n        Args:\n            config: Dictionary of the imported configuration from yaml files.\n            condition_functions_dict: A dictionary where k:condition id, v:Callable (validation function).\n\n        Returns:\n            A dictionary of StandardCondition instances (k: condition id, v: StandardCondition instance).\n        \"\"\"\n        # Var init.\n        conditions_dict: dict[str, StandardCondition] = {}\n\n        # Condition configuration (under conditions' key)\n        conditions_conf: dict[str, dict[str, Any]] = config[self.CONST_STD_CONDITIONS_CONF_KEY]\n\n        # Looping through conditions (inside a group)\n        for condition_id, condition_params in conditions_conf.items():\n            # Get condition validation function\n            validation_function_name: str = condition_params[self.CONST_CONDITION_VALIDATION_FUNCTION_CONF_KEY]\n\n            if validation_function_name not in condition_functions_dict:\n                raise KeyError(f\"Unknwown validation function : {validation_function_name}\")\n\n            # Get Callable from function name\n            validation_function: Callable = condition_functions_dict[validation_function_name]\n\n            # Create Condition instance\n            condition_instance: StandardCondition = StandardCondition(\n                condition_id=condition_id,\n                description=condition_params[self.CONST_CONDITION_DESCRIPTION_CONF_KEY],\n                validation_function=validation_function,\n                validation_function_parameters=condition_params[self.CONST_CONDITION_VALIDATION_PARAMETERS_CONF_KEY],\n            )\n            conditions_dict[condition_id] = condition_instance\n\n        return conditions_dict\n\n    def _adapt_user_rules_dict(self, rules_dict: dict[str, dict[str, Any]]) -> dict[str, dict[str, list[Any]]]:\n        \"\"\"(Protected)\n        Return a dictionary of Rule's instances built from user's rules dictionary.\n\n        Args:\n            rules_dict: User raw rules dictionary.\n\n        Returns:\n            A rules dictionary made from the user input rules.\n        \"\"\"\n        # Var init.\n        rules_dict_formatted: dict[str, list[Any]] = {}\n\n        # Looping throught groups\n        for group_id, group_rules in rules_dict.items():\n            # Initialize list or rules in the group\n            rules_dict_formatted[group_id] = []\n\n            # Looping through rules (inside a group)\n            for rule_id, rule_dict in group_rules.items():\n                # Get action function\n                action = rule_dict[\"action\"]\n\n                # Trigger if not **kwargs\n                if \"kwargs\" not in inspect.signature(action).parameters:\n                    raise KeyError(f\"The action function {action} must have a '**kwargs' parameter.\")\n\n                # Create Rule instance\n                rule = Rule(\n                    set_id=self.CONST_DFLT_RULE_SET_ID,\n                    group_id=group_id,\n                    rule_id=rule_id,\n                    action=action,\n                    action_parameters=rule_dict.get(self.CONST_ACTION_PARAMETERS_CONF_KEY),\n                    condition_exprs={self.CONST_STD_RULE_CONDITION_CONF_KEY: self.CONST_USER_CONDITION_STRING}\n                    if self.CONST_STD_RULE_CONDITION_CONF_KEY in rule_dict\n                    and rule_dict.get(self.CONST_STD_RULE_CONDITION_CONF_KEY) is not None\n                    else {self.CONST_STD_RULE_CONDITION_CONF_KEY: None},\n                    std_condition_instances={\n                        self.CONST_USER_CONDITION_STRING: StandardCondition(\n                            condition_id=self.CONST_USER_CONDITION_STRING,\n                            description=\"Automatic description\",\n                            validation_function=rule_dict.get(self.CONST_STD_RULE_CONDITION_CONF_KEY),\n                            validation_function_parameters=rule_dict.get(\n                                self.CONST_CONDITION_VALIDATION_PARAMETERS_CONF_KEY\n                            ),\n                        )\n                    },\n                    condition_factory_mapping=self.BUILTIN_FACTORY_MAPPING,\n                )\n                rules_dict_formatted[group_id].append(rule)\n\n        return {self.CONST_DFLT_RULE_SET_ID: rules_dict_formatted}\n\n    def __str__(self) -> str:\n        \"\"\"Object human string representation (called by str()).\n\n        Returns:\n            A string representation of the instance.\n        \"\"\"\n        # Vars init.\n        attrs_str: str = \"\"\n\n        # Get some instance attributes infos\n        class_name: str = self.__class__.__name__\n        attrs: list[tuple[str, Any]] = [\n            attr\n            for attr in inspect.getmembers(self)\n            if not (\n                attr[0].startswith(\"_\")\n                or attr[0].startswith(\"CONST_\")\n                or isinstance(attr[1], (FunctionType, MethodType))\n            )\n        ]\n\n        # Build string representation\n        for attr, val in attrs:\n            attrs_str += f\"{attr}={str(val)}, \"\n\n        return f\"{class_name}({attrs_str})\"\n
    "},{"location":"api_reference/#arta._engine.RulesEngine.__init__","title":"__init__(*, rules_dict=None, config_path=None)","text":"

    Initialize the rules.

    2 possibilities: either 'rules_dict', or 'config_path', not both.

    Parameters:

    Name Type Description Default rules_dict dict[str, dict[str, Any]] | None

    A dictionary containing the rules' definitions.

    None config_path str | None

    Path of a directory containing the YAML files.

    None

    Raises:

    Type Description KeyError

    Key not found.

    TypeError

    Wrong type.

    Source code in arta/_engine.py
    def __init__(\n    self,\n    *,\n    rules_dict: dict[str, dict[str, Any]] | None = None,\n    config_path: str | None = None,\n) -> None:\n    \"\"\"Initialize the rules.\n\n    2 possibilities: either 'rules_dict', or 'config_path', not both.\n\n    Args:\n        rules_dict: A dictionary containing the rules' definitions.\n        config_path: Path of a directory containing the YAML files.\n\n    Raises:\n        KeyError: Key not found.\n        TypeError: Wrong type.\n    \"\"\"\n    # Var init.\n    factory_mapping_classes: dict[str, type[BaseCondition]] = {}\n    std_condition_instances: dict[str, StandardCondition] = {}\n\n    if config_path is not None and rules_dict is not None:\n        raise ValueError(\"RulesEngine takes only one parameter: 'rules_dict' or 'config_path', not both.\")\n\n    # Init. default parsing_error_strategy (probably not needed because already defined elsewhere)\n    self._parsing_error_strategy: ParsingErrorStrategy = ParsingErrorStrategy.RAISE\n\n    # Initialize directly with a rules dict\n    if rules_dict is not None:\n        # Data validation\n        RulesDict.parse_obj(rules_dict)\n\n        # Edge cases data validation\n        if not isinstance(rules_dict, dict):\n            raise TypeError(f\"'rules_dict' must be dict type, not '{type(rules_dict)}'\")\n        elif len(rules_dict) == 0:\n            raise KeyError(\"'rules_dict' couldn't be empty.\")\n\n        # Attribute definition\n        self.rules: dict[str, dict[str, list[Rule]]] = self._adapt_user_rules_dict(rules_dict)\n\n    # Initialize with a config_path\n    elif config_path is not None:\n        # Load config in attribute\n        config_dict: dict[str, Any] = load_config(config_path)\n\n        # Data validation\n        config: Configuration = Configuration(**config_dict)\n\n        if config.parsing_error_strategy is not None:\n            # Set parsing error handling strategy from config\n            self._parsing_error_strategy = ParsingErrorStrategy(config.parsing_error_strategy)\n\n        # dict of available action functions (k: function name, v: function object)\n        action_modules: list[str] = config.actions_source_modules\n        action_functions: dict[str, Callable] = self._get_object_from_source_modules(action_modules)\n\n        # dict of available standard condition functions (k: function name, v: function object)\n        condition_modules: list[str] = (\n            config.conditions_source_modules if config.conditions_source_modules is not None else []\n        )\n        std_condition_functions: dict[str, Callable] = self._get_object_from_source_modules(condition_modules)\n\n        # Dictionary of condition instances (k: condition id, v: instance), built from config data\n        if len(std_condition_functions) > 0:\n            std_condition_instances = self._build_std_conditions(\n                config=config.dict(), condition_functions_dict=std_condition_functions\n            )\n\n        # User-defined/custom conditions\n        if config.condition_factory_mapping is not None and config.custom_classes_source_modules is not None:\n            # dict of custom condition classes (k: classe name, v: class object)\n            custom_condition_classes: dict[str, type[BaseCondition]] = self._get_object_from_source_modules(\n                config.custom_classes_source_modules\n            )\n\n            # Build a factory mapping dictionary (k: conf key, v:class object)\n            factory_mapping_classes.update(\n                {\n                    conf_key: custom_condition_classes[class_name]\n                    for conf_key, class_name in config.condition_factory_mapping.items()\n                }\n            )\n\n        # Arta built-in conditions\n        factory_mapping_classes.update(self.BUILTIN_FACTORY_MAPPING)\n\n        # Attribute definition\n        self.rules = self._build_rules(\n            std_condition_instances=std_condition_instances,\n            action_functions=action_functions,\n            config=config.dict(),\n            factory_mapping_classes=factory_mapping_classes,\n        )\n    else:\n        raise ValueError(\"RulesEngine needs a parameter: 'rule_dict' or 'config_path'.\")\n
    "},{"location":"api_reference/#arta._engine.RulesEngine.__str__","title":"__str__()","text":"

    Object human string representation (called by str()).

    Returns:

    Type Description str

    A string representation of the instance.

    Source code in arta/_engine.py
    def __str__(self) -> str:\n    \"\"\"Object human string representation (called by str()).\n\n    Returns:\n        A string representation of the instance.\n    \"\"\"\n    # Vars init.\n    attrs_str: str = \"\"\n\n    # Get some instance attributes infos\n    class_name: str = self.__class__.__name__\n    attrs: list[tuple[str, Any]] = [\n        attr\n        for attr in inspect.getmembers(self)\n        if not (\n            attr[0].startswith(\"_\")\n            or attr[0].startswith(\"CONST_\")\n            or isinstance(attr[1], (FunctionType, MethodType))\n        )\n    ]\n\n    # Build string representation\n    for attr, val in attrs:\n        attrs_str += f\"{attr}={str(val)}, \"\n\n    return f\"{class_name}({attrs_str})\"\n
    "},{"location":"api_reference/#arta._engine.RulesEngine.apply_rules","title":"apply_rules(input_data, *, rule_set=None, verbose=False, **kwargs)","text":"

    Apply the rules and return results.

    For each rule group of a given rule set, rules are applied sequentially, The loop is broken when a rule is applied (an action is triggered). Then, the next rule group is evaluated. And so on...

    This means that the order of the rules in the configuration file (e.g., rules.yaml) is meaningful.

    Parameters:

    Name Type Description Default input_data dict[str, Any]

    Input data to apply rules on.

    required rule_set str | None

    Apply rules associated with the specified rule set.

    None verbose bool

    If True, add extra ids (group_id, rule_id) for result explicability.

    False **kwargs Any

    For user extra arguments.

    {}

    Returns:

    Type Description dict[str, Any]

    A dictionary containing the rule groups' results (k: group id, v: action result).

    Raises:

    Type Description TypeError

    Wrong type.

    KeyError

    Key not found.

    Source code in arta/_engine.py
    def apply_rules(\n    self, input_data: dict[str, Any], *, rule_set: str | None = None, verbose: bool = False, **kwargs: Any\n) -> dict[str, Any]:\n    \"\"\"Apply the rules and return results.\n\n    For each rule group of a given rule set, rules are applied sequentially,\n    The loop is broken when a rule is applied (an action is triggered).\n    Then, the next rule group is evaluated.\n    And so on...\n\n    This means that the order of the rules in the configuration file\n    (e.g., rules.yaml) is meaningful.\n\n    Args:\n        input_data: Input data to apply rules on.\n        rule_set: Apply rules associated with the specified rule set.\n        verbose: If True, add extra ids (group_id, rule_id) for result explicability.\n        **kwargs: For user extra arguments.\n\n    Returns:\n        A dictionary containing the rule groups' results (k: group id, v: action result).\n\n    Raises:\n        TypeError: Wrong type.\n        KeyError: Key not found.\n    \"\"\"\n    # Input_data validation\n    if not isinstance(input_data, dict):\n        raise TypeError(f\"'input_data' must be dict type, not '{type(input_data)}'\")\n    elif len(input_data) == 0:\n        raise KeyError(\"'input_data' couldn't be empty.\")\n\n    # Var init.\n    input_data_copy: dict[str, Any] = copy.deepcopy(input_data)\n\n    # Prepare the result key\n    input_data_copy[\"output\"] = {}\n\n    # If there is no given rule set param. and there is only one rule set in self.rules\n    # and its value is 'default_rule_set', look for this one (rule_set='default_rule_set')\n    if rule_set is None and len(self.rules) == 1 and self.rules.get(self.CONST_DFLT_RULE_SET_ID) is not None:\n        rule_set = self.CONST_DFLT_RULE_SET_ID\n\n    # Check if given rule set is in self.rules?\n    if rule_set not in self.rules:\n        raise KeyError(\n            f\"Rule set '{rule_set}' not found in the rules, available rule sets are : {list(self.rules.keys())}.\"\n        )\n\n    # Var init.\n    results_dict: dict[str, Any] = {\"verbosity\": {\"rule_set\": rule_set, \"results\": []}}\n\n    # Groups' loop\n    for group_id, rules_list in self.rules[rule_set].items():\n        # Initialize result of the rule group with None\n        results_dict[group_id] = None\n\n        # Rules' loop (inside a group)\n        for rule in rules_list:\n            # Apply rules\n            action_result, rule_details = rule.apply(\n                input_data_copy, parsing_error_strategy=self._parsing_error_strategy, **kwargs\n            )\n\n            # Check if the rule has been applied (= action activated)\n            if \"action_result\" in rule_details:\n                # Save result and details\n                results_dict[group_id] = action_result\n                results_dict[\"verbosity\"][\"results\"].append(rule_details)\n\n                # Update input data with current result with key 'output' (can be used in next rules)\n                input_data_copy[\"output\"][group_id] = copy.deepcopy(results_dict[group_id])\n\n                # We can only have one result per group => break when \"action_result\" in rule_details\n                break\n\n    # Handling non-verbose mode\n    if not verbose:\n        results_dict.pop(\"verbosity\")\n\n    return results_dict\n
    "},{"location":"api_reference/#conditionpy","title":"condition.py","text":"

    Condition implementation.

    Classes: BaseCondition, StandardCondition, SimpleCondition

    "},{"location":"api_reference/#arta.condition.BaseCondition","title":"BaseCondition","text":"

    Bases: ABC

    Base class of a Condition object (Strategy Pattern).

    Is an abstract class and can't be instantiated.

    Attributes:

    Name Type Description condition_id

    Id of a condition.

    description

    Description of a condition.

    validation_function

    Validation function of a condition.

    validation_function_parameters

    Arguments of the validation function.

    Source code in arta/condition.py
    class BaseCondition(ABC):\n    \"\"\"Base class of a Condition object (Strategy Pattern).\n\n    Is an abstract class and can't be instantiated.\n\n    Attributes:\n        condition_id: Id of a condition.\n        description: Description of a condition.\n        validation_function: Validation function of a condition.\n        validation_function_parameters: Arguments of the validation function.\n    \"\"\"\n\n    # Class constants\n    CONST_CONDITION_DATA_LABEL: str = \"Custom condition data (not needed)\"\n    CONDITION_ID_PATTERN: str = UPPERCASE_WORD_PATTERN\n\n    def __init__(\n        self,\n        condition_id: str,\n        description: str,\n        validation_function: Callable | None = None,\n        validation_function_parameters: dict[str, Any] | None = None,\n    ) -> None:\n        \"\"\"\n        Initialize attributes.\n\n        Args:\n            condition_id: Id of a condition.\n            description: Description of a condition.\n            validation_function: Validation function of a condition.\n            validation_function_parameters: Arguments of the validation function.\n        \"\"\"\n        self._condition_id = condition_id  # NOSONAR\n        self._description = description  # NOSONAR\n        self._validation_function = validation_function  # NOSONAR\n        self._validation_function_parameters = validation_function_parameters  # NOSONAR\n\n    @classmethod\n    def extract_condition_ids_from_expression(cls, condition_expr: str | None = None) -> set[str]:\n        \"\"\"Get the condition ids from a string (e.g., UPPERCASE words).\n\n        E.g., CONDITION_1 and not CONDITION_2\n\n        Warning: implementation is based on the current class constant CONDITION_SPLIT_PATTERN.\n\n        Args:\n            condition_expr: A boolean expression (string).\n\n        Returns:\n            A set of extracted condition ids.\n        \"\"\"\n        cond_ids: set[str] = set()\n\n        if condition_expr is not None:\n            cond_ids = set(re.findall(cls.CONDITION_ID_PATTERN, condition_expr))\n\n        return cond_ids\n\n    @abstractmethod\n    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n        \"\"\"(Abstract)\n        Return True if the condition is verified.\n\n        Args:\n            input_data: Input data to apply rules on.\n            parsing_error_strategy: Error handling strategy for parameter parsing.\n            **kwargs: For user extra arguments.\n\n        Returns:\n            True if the condition is verified, otherwise False.\n        \"\"\"\n        raise NotImplementedError\n
    "},{"location":"api_reference/#arta.condition.BaseCondition.__init__","title":"__init__(condition_id, description, validation_function=None, validation_function_parameters=None)","text":"

    Initialize attributes.

    Parameters:

    Name Type Description Default condition_id str

    Id of a condition.

    required description str

    Description of a condition.

    required validation_function Callable | None

    Validation function of a condition.

    None validation_function_parameters dict[str, Any] | None

    Arguments of the validation function.

    None Source code in arta/condition.py
    def __init__(\n    self,\n    condition_id: str,\n    description: str,\n    validation_function: Callable | None = None,\n    validation_function_parameters: dict[str, Any] | None = None,\n) -> None:\n    \"\"\"\n    Initialize attributes.\n\n    Args:\n        condition_id: Id of a condition.\n        description: Description of a condition.\n        validation_function: Validation function of a condition.\n        validation_function_parameters: Arguments of the validation function.\n    \"\"\"\n    self._condition_id = condition_id  # NOSONAR\n    self._description = description  # NOSONAR\n    self._validation_function = validation_function  # NOSONAR\n    self._validation_function_parameters = validation_function_parameters  # NOSONAR\n
    "},{"location":"api_reference/#arta.condition.BaseCondition.extract_condition_ids_from_expression","title":"extract_condition_ids_from_expression(condition_expr=None) classmethod","text":"

    Get the condition ids from a string (e.g., UPPERCASE words).

    E.g., CONDITION_1 and not CONDITION_2

    Warning: implementation is based on the current class constant CONDITION_SPLIT_PATTERN.

    Parameters:

    Name Type Description Default condition_expr str | None

    A boolean expression (string).

    None

    Returns:

    Type Description set[str]

    A set of extracted condition ids.

    Source code in arta/condition.py
    @classmethod\ndef extract_condition_ids_from_expression(cls, condition_expr: str | None = None) -> set[str]:\n    \"\"\"Get the condition ids from a string (e.g., UPPERCASE words).\n\n    E.g., CONDITION_1 and not CONDITION_2\n\n    Warning: implementation is based on the current class constant CONDITION_SPLIT_PATTERN.\n\n    Args:\n        condition_expr: A boolean expression (string).\n\n    Returns:\n        A set of extracted condition ids.\n    \"\"\"\n    cond_ids: set[str] = set()\n\n    if condition_expr is not None:\n        cond_ids = set(re.findall(cls.CONDITION_ID_PATTERN, condition_expr))\n\n    return cond_ids\n
    "},{"location":"api_reference/#arta.condition.BaseCondition.verify","title":"verify(input_data, parsing_error_strategy, **kwargs) abstractmethod","text":"

    (Abstract) Return True if the condition is verified.

    Parameters:

    Name Type Description Default input_data dict[str, Any]

    Input data to apply rules on.

    required parsing_error_strategy ParsingErrorStrategy

    Error handling strategy for parameter parsing.

    required **kwargs Any

    For user extra arguments.

    {}

    Returns:

    Type Description bool

    True if the condition is verified, otherwise False.

    Source code in arta/condition.py
    @abstractmethod\ndef verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n    \"\"\"(Abstract)\n    Return True if the condition is verified.\n\n    Args:\n        input_data: Input data to apply rules on.\n        parsing_error_strategy: Error handling strategy for parameter parsing.\n        **kwargs: For user extra arguments.\n\n    Returns:\n        True if the condition is verified, otherwise False.\n    \"\"\"\n    raise NotImplementedError\n
    "},{"location":"api_reference/#arta.condition.SimpleCondition","title":"SimpleCondition","text":"

    Bases: BaseCondition

    Class implementing a built-in simple condition.

    Attributes:

    Name Type Description condition_id

    Id of a condition.

    description

    Description of a condition.

    validation_function

    Validation function of a condition.

    validation_function_parameters

    Arguments of the validation function.

    Source code in arta/condition.py
    class SimpleCondition(BaseCondition):\n    \"\"\"Class implementing a built-in simple condition.\n\n    Attributes:\n        condition_id: Id of a condition.\n        description: Description of a condition.\n        validation_function: Validation function of a condition.\n        validation_function_parameters: Arguments of the validation function.\n    \"\"\"\n\n    # Class constants\n    CONST_CUSTOM_CONDITION_DATA_LABEL: str = \"Simple condition data (not needed)\"\n    CONDITION_ID_PATTERN: str = r\"(?:input\\.|output\\.)(?:[a-z_\\-0-9!=<>\\\"NTF\\.]*)\"\n\n    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n        \"\"\"Return True if the condition is verified.\n\n        Example of a unitary simple condition to be verified: 'input.age>=100'\n\n        Args:\n            input_data: Request or input data to apply rules on.\n            parsing_error_strategy: Error handling strategy for parameter parsing.\n            **kwargs: For user extra arguments.\n\n        Returns:\n            True if the condition is verified, otherwise False.\n\n        Raises:\n            AttributeError: Check the validation function or its parameters.\n        \"\"\"\n        bool_var: bool = False\n        unitary_expr: str = self._condition_id\n\n        data_path_patt: str = r\"(?:input\\.|output\\.)(?:[a-z_\\-\\.]*)\"\n\n        # Retrieve only the data path\n        path_matches: list[str] = re.findall(data_path_patt, unitary_expr)\n\n        if len(path_matches) == 1:\n            # Regular case: we have a data_path\n            data_path: str = path_matches[0]\n\n            # Read data from its path\n            data = parse_dynamic_parameter(  # noqa\n                parameter=data_path, input_data=input_data, parsing_error_strategy=parsing_error_strategy\n            )\n\n            # Replace with the variable name in the expression\n            eval_expr: str = unitary_expr.replace(data_path, \"data\")\n\n            # Evaluate the expression\n            try:\n                bool_var = eval(eval_expr)  # noqa\n            except TypeError:\n                # Ignore evaluation --> False\n                pass\n\n        elif parsing_error_strategy == ParsingErrorStrategy.RAISE:\n            # Raise an error because of no match for a data path\n            raise ConditionExecutionError(f\"Error when verifying simple condition: '{unitary_expr}'\")\n\n        else:\n            # Other case: ignore, default value => return False\n            pass\n\n        return bool_var\n
    "},{"location":"api_reference/#arta.condition.SimpleCondition.verify","title":"verify(input_data, parsing_error_strategy, **kwargs)","text":"

    Return True if the condition is verified.

    Example of a unitary simple condition to be verified: 'input.age>=100'

    Parameters:

    Name Type Description Default input_data dict[str, Any]

    Request or input data to apply rules on.

    required parsing_error_strategy ParsingErrorStrategy

    Error handling strategy for parameter parsing.

    required **kwargs Any

    For user extra arguments.

    {}

    Returns:

    Type Description bool

    True if the condition is verified, otherwise False.

    Raises:

    Type Description AttributeError

    Check the validation function or its parameters.

    Source code in arta/condition.py
    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n    \"\"\"Return True if the condition is verified.\n\n    Example of a unitary simple condition to be verified: 'input.age>=100'\n\n    Args:\n        input_data: Request or input data to apply rules on.\n        parsing_error_strategy: Error handling strategy for parameter parsing.\n        **kwargs: For user extra arguments.\n\n    Returns:\n        True if the condition is verified, otherwise False.\n\n    Raises:\n        AttributeError: Check the validation function or its parameters.\n    \"\"\"\n    bool_var: bool = False\n    unitary_expr: str = self._condition_id\n\n    data_path_patt: str = r\"(?:input\\.|output\\.)(?:[a-z_\\-\\.]*)\"\n\n    # Retrieve only the data path\n    path_matches: list[str] = re.findall(data_path_patt, unitary_expr)\n\n    if len(path_matches) == 1:\n        # Regular case: we have a data_path\n        data_path: str = path_matches[0]\n\n        # Read data from its path\n        data = parse_dynamic_parameter(  # noqa\n            parameter=data_path, input_data=input_data, parsing_error_strategy=parsing_error_strategy\n        )\n\n        # Replace with the variable name in the expression\n        eval_expr: str = unitary_expr.replace(data_path, \"data\")\n\n        # Evaluate the expression\n        try:\n            bool_var = eval(eval_expr)  # noqa\n        except TypeError:\n            # Ignore evaluation --> False\n            pass\n\n    elif parsing_error_strategy == ParsingErrorStrategy.RAISE:\n        # Raise an error because of no match for a data path\n        raise ConditionExecutionError(f\"Error when verifying simple condition: '{unitary_expr}'\")\n\n    else:\n        # Other case: ignore, default value => return False\n        pass\n\n    return bool_var\n
    "},{"location":"api_reference/#arta.condition.StandardCondition","title":"StandardCondition","text":"

    Bases: BaseCondition

    Class implementing a built-in condition, named standard condition.

    Attributes:

    Name Type Description condition_id

    Id of a condition.

    description

    Description of a condition.

    validation_function

    Validation function of a condition.

    validation_function_parameters

    Arguments of the validation function.

    Source code in arta/condition.py
    class StandardCondition(BaseCondition):\n    \"\"\"Class implementing a built-in condition, named standard condition.\n\n    Attributes:\n        condition_id: Id of a condition.\n        description: Description of a condition.\n        validation_function: Validation function of a condition.\n        validation_function_parameters: Arguments of the validation function.\n    \"\"\"\n\n    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n        \"\"\"Return True if the condition is verified.\n\n        Example of a unitary standard condition: CONDITION_1\n\n        Args:\n            input_data: Request or input data to apply rules on.\n            parsing_error_strategy: Error handling strategy for parameter parsing.\n            **kwargs: For user extra arguments.\n\n        Returns:\n            True if the condition is verified, otherwise False.\n\n        Raises:\n            AttributeError: Check the validation function or its parameters.\n        \"\"\"\n        if self._validation_function is None:\n            raise AttributeError(\"Validation function should not be None\")\n\n        if self._validation_function_parameters is None:\n            raise AttributeError(\"Validation function parameters should not be None\")\n\n        # Parse dynamic parameters\n        parameters: dict[str, Any] = {}\n\n        for key, value in self._validation_function_parameters.items():\n            parameters[key] = parse_dynamic_parameter(\n                parameter=value, input_data=input_data, parsing_error_strategy=parsing_error_strategy\n            )\n\n        # Run validation_function\n        return self._validation_function(**parameters)\n
    "},{"location":"api_reference/#arta.condition.StandardCondition.verify","title":"verify(input_data, parsing_error_strategy, **kwargs)","text":"

    Return True if the condition is verified.

    Example of a unitary standard condition: CONDITION_1

    Parameters:

    Name Type Description Default input_data dict[str, Any]

    Request or input data to apply rules on.

    required parsing_error_strategy ParsingErrorStrategy

    Error handling strategy for parameter parsing.

    required **kwargs Any

    For user extra arguments.

    {}

    Returns:

    Type Description bool

    True if the condition is verified, otherwise False.

    Raises:

    Type Description AttributeError

    Check the validation function or its parameters.

    Source code in arta/condition.py
    def verify(self, input_data: dict[str, Any], parsing_error_strategy: ParsingErrorStrategy, **kwargs: Any) -> bool:\n    \"\"\"Return True if the condition is verified.\n\n    Example of a unitary standard condition: CONDITION_1\n\n    Args:\n        input_data: Request or input data to apply rules on.\n        parsing_error_strategy: Error handling strategy for parameter parsing.\n        **kwargs: For user extra arguments.\n\n    Returns:\n        True if the condition is verified, otherwise False.\n\n    Raises:\n        AttributeError: Check the validation function or its parameters.\n    \"\"\"\n    if self._validation_function is None:\n        raise AttributeError(\"Validation function should not be None\")\n\n    if self._validation_function_parameters is None:\n        raise AttributeError(\"Validation function parameters should not be None\")\n\n    # Parse dynamic parameters\n    parameters: dict[str, Any] = {}\n\n    for key, value in self._validation_function_parameters.items():\n        parameters[key] = parse_dynamic_parameter(\n            parameter=value, input_data=input_data, parsing_error_strategy=parsing_error_strategy\n        )\n\n    # Run validation_function\n    return self._validation_function(**parameters)\n
    "},{"location":"glossary/","title":"Glossary","text":"Concept Definition action A task which is executed when conditions are verified. action function A callable object called to execute the action. action parameter Parameter of an action function. condition A condition to be verified before executing an action. condition id Identifier of a single condition (must be in CAPITAL LETTER). condition expression A boolean expression combining several conditions (meaning several condition id). condition function A callable object called to be verified therefore it returns a boolean. condition parameter Parameter of a condition/validation function. custom condition A user-defined condition. rule A set of conditions combined to one action. rule group A group of rules (usually sharing a common context). rule id Identifier of a single rule. rule set A set of rule groups (mostly one: default_rule_set). simple condition A built-in very simple condition. standard condition The regular built-in condition. validation function Same thing as a condition function."},{"location":"home/","title":"Home","text":"

    An Open Source Rules Engine - Make rule handling simple

    "},{"location":"home/#welcome-to-the-documentation","title":"Welcome to the documentation","text":"

    New feature

    Check out the new and very convenient feature called the simple condition. A new and lightweight way of configuring your rules' conditions.

    Arta is automatically tested with:

    Releases

    Want to see last updates, check the Release notes

    Pydantic 2

    Arta is now working with Pydantic 2! And of course, Pydantic 1 as well.

    "},{"location":"how_to/","title":"How to","text":"

    Ensure that you have correctly installed Arta before, check the Installation page

    "},{"location":"how_to/#simple-condition","title":"Simple condition","text":"

    Beta feature

    Simple condition is still a beta feature, some cases could not work as designed.

    Simple conditions are a new and straightforward way of configuring your conditions.

    It simplifies a lot your rules by:

    Note

    With the simple conditions you use straight boolean expressions directly in your configuration.

    It is easyer to read and maintain

    The configuration key here is:

    simple_condition:

    Example :

    ---\nrules:\n  default_rule_set:\n    admission:\n      ADM_OK:\n        simple_condition: input.power==\"strength\" or input.power==\"fly\"\n        action: set_admission\n        action_parameters:\n          value: OK \n      ADM_TO_BE_CHECKED:\n        simple_condition: input.age>=150 and input.age!=None\n        action: set_admission\n        action_parameters:\n          value: TO_CHECK     \n      ADM_KO:\n        simple_condition: null\n        action: set_admission\n        action_parameters:\n          value: KO\n\nactions_source_modules:\n  - my_folder.actions  # (1)\n
    1. Contains action function implementations, no need of the key conditions_source_modules here.

    How to write a simple condition like:

    input.power==\"strength\" or input.power==\"fly\"\n

    Warning

    Security concern

    Python code injection:

    Because Arta is using the eval() built-in function to evaluate simple conditions:

    "},{"location":"how_to/#standard-condition","title":"Standard condition","text":"

    It is the first implemented way of using Arta and probably the most powerful.

    The configuration key here is:

    condition:

    YAML

    The built-in file format used by Arta for configuration is YAML.

    Enhancement proposal

    We are thinking on a design that will allow user-implemented loading of the configuration (e.g., if you prefer using a JSON format). Stay tuned.

    "},{"location":"how_to/#yaml-file","title":"YAML file","text":"

    Simple Conditions

    The following YAML example illustrates how to configure usual standard conditions but there is another and simpler way to do it by using a special feature: the simple condition.

    Create a YAML file and define your rules like this:

    ---\nrules:\n  default_rule_set:  # (1)\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER  # (2)\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:  # (3)\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n\nconditions_source_modules:  # (4)\n  - my_folder.conditions\nactions_source_modules:  # (5)\n  - my_folder.actions\n
    1. This is the name of your rule set (i.e., default_rule_set is by default).
    2. You can't set a callable object here so we need to use a condition id.
    3. The conditions are identified by an id and defined here. The validation function is defined in a user's python module.
    4. This is the path of the module where the validation functions are implemented (you must change it).
    5. This is the path of the module where the action functions are implemented (you must change it).

    Warning

    Condition ids must be in capital letters here, it is mandatory (e.g., HAS_SCHOOL_AUTHORIZED_POWER).

    Tip

    You can split your configuration in multiple YAML files seamlessly in order to keep things clear. Example:

    It's very convenient when you have a lot of different rules and conditions in your app.

    "},{"location":"how_to/#condition-expression","title":"Condition expression","text":"

    In the above YAML, the following condition expression is intentionally very simple:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n

    The key condition: can take one condition id but also a condition expression (i.e., a boolean expression of condition ids) combining several conditions:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: (HAS_SCHOOL_AUTHORIZED_POWER or SPEAKS_FRENCH) and not(IS_EVIL)\n        action: set_admission\n        action_parameters:\n          value: true\n

    Warning

    In that example, you must define the 3 conditions in the configuration:

    Tip

    Use the condition expressions to keep things simple. Put your conditions in one expression as you can rather than creating several rules

    "},{"location":"how_to/#functions","title":"Functions","text":"

    We must create 2 modules:

    Note

    Module names are arbitrary, you can choose what you want.

    And implement our 2 needed validation and action functions (the one defined in the configuration file):

    conditions.py:

    def has_authorized_super_power(power):\n    return power in [\"strength\", \"fly\", \"immortality\"]\n

    actions.py:

    def set_admission(value, **kwargs):  # (1)\n    return {\"is_admitted\": value}\n
    1. **kwargs is mandatory here.

    Warning

    Function name and parameters must be the same as the one configured in the YAML file.

    "},{"location":"how_to/#usage","title":"Usage","text":"

    Once your configuration file and your functions are ready, you can use it very simply:

    from arta import RulesEngine\n\ninput_data = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"super_power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n\neng = RulesEngine(config_path=\"path/to/conf/dir\")\n\nresult = eng.apply_rules(input_data)\n\nprint(result)\n

    You should get:

    {'check_admission': {'is_admitted': True}}

    API Documentation

    You can get details on the RulesEngine parameters in the API Reference.

    "},{"location":"how_to/#concepts","title":"Concepts","text":"

    Let's go deeper into the concepts.

    "},{"location":"how_to/#rule-set-and-rule-group","title":"Rule set and rule group","text":"

    A rule set is composed of rule groups which are themselves composed of rules. We can find this tree structure in the following YAML:

    ---\nrules:\n  default_rule_set:  # (1)\n    check_admission:  # (2)\n      ADMITTED_RULE:  # (3)\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n
    1. This is the id of the rule set.
    2. This key define a rule group, we can have many groups (we have only one here for simplicity).
    3. This key is a rule id, which identifies rules among others.
    "},{"location":"how_to/#rule","title":"Rule","text":"

    Rule definitions are identified by an id (e.g., ADMITTED_RULE):

          ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n

    Tip

    Rule ids are in capital letters for readability only: it is an advised practice.

    Rules are made of 2 different things:

          ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n
          ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n
    "},{"location":"how_to/#condition-and-action","title":"Condition and Action","text":"

    Conditions and actions are quite similar in terms of implementation but their goals are different.

    Both are made of a callable object and some parameters:

    Parameter's special syntax

    The action and condition arguments can have a special syntax:

    condition_parameters:\n  power: input.super_power\n

    The string input.super_power is evaluated by the rules engine and it means \"fetch the key super_power in the input data\".

    "},{"location":"how_to/#dictionary-rules","title":"Dictionary rules","text":"

    Rules can be configured in a YAML file but they can also be defined by a regular dictionary:

    Without type hintsWith type hints (>=3.9)
    from arta import RulesEngine\n\nset_admission = lambda value, **kwargs: {\"is_admitted\": value}\n\nrules = {\n    \"check_admission\": {\n        \"ADMITTED_RULE\": {\n            \"condition\": lambda power: power in [\"strength\", \"fly\", \"immortality\"],\n            \"condition_parameters\": {\"power\": \"input.super_power\"}, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": True},\n        },\n        \"DEFAULT_RULE\": {\n            \"condition\": None,\n            \"condition_parameters\": None, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": False},\n        },\n    }\n}\n\ninput_data = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"super_power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n\neng = RulesEngine(rules_dict=rules)\n\nresult = eng.apply_rules(input_data)\n\nprint(result)\n
    from typing import Any, Callable\n\nfrom arta import RulesEngine\n\nset_admission: Callable = lambda value, **kwargs: {\"is_admitted\": value}\n\nrules: dict[str, Any] = {\n    \"check_admission\": {\n        \"ADMITTED_RULE\": {\n            \"condition\": lambda power: power in [\"strength\", \"fly\", \"immortality\"],\n            \"condition_parameters\": {\"power\": \"input.super_power\"}, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": True},\n        },\n        \"DEFAULT_RULE\": {\n            \"condition\": None,\n            \"condition_parameters\": None, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": False},\n        },\n    }\n}\n\ninput_data: dict[str, Any] = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"super_power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n\neng = RulesEngine(rules_dict=rules)\n\nresult: dict[str, Any] = eng.apply_rules(input_data)\n\nprint(result)\n

    You should get:

    {'check_admission': {'is_admitted': True}}\n

    Success

    Superman is admitted to the superhero school!

    Well done! By executing this code you have:

    1. Defined an action function (set_admission)
    2. Defined a rule set (rules)
    3. Used some input data (input_data)
    4. Instanciated a rules engine (RulesEngine)
    5. Applied the rules on the data and get some results (.apply_rules())

    Note

    In the code example we used some anonymous/lambda function for simplicity but it could be regular python functions as well.

    YAML vs Dictionary

    How to choose between dictionary and configuration?

    In most cases, you must choose the configuration way of defining your rules.

    You will improve your rules' maintainability a lot. In some cases like proof-of-concepts or Jupyter notebook works, you will probably be happy with straightforward dictionaries.

    Arta has plenty more features to discover. If you want to learn more, go to the next chapter: Advanced User Guide.

    "},{"location":"installation/","title":"Installation","text":""},{"location":"installation/#python","title":"Python","text":"

    Compatible with:

    "},{"location":"installation/#pip","title":"pip","text":"

    In your python environment:

    "},{"location":"installation/#regular-use","title":"Regular use","text":"
    pip install arta\n
    "},{"location":"installation/#development","title":"Development","text":"
    pip install arta[all]\n
    "},{"location":"parameters/","title":"Parameters","text":""},{"location":"parameters/#parsing-prefix-keywords","title":"Parsing prefix keywords","text":"

    There is 2 allowed parsing prefix keywords:

    Here are examples:

    1. input.name: maps to input_data[\"name\"].
    2. output.check_admission.is_admitted: maps to result[\"check_admission\"][\"is_admitted\"].

    They both can be used in condition and action parameters.

    Info

    A value without any prefix keyword is a constant.

    "},{"location":"parameters/#parsing-error","title":"Parsing error","text":""},{"location":"parameters/#raise-by-default","title":"Raise by default","text":"

    By default, errors during condition and action parameters parsing are raised.

    If we refer to the dictionary example:

    rules = {\n    \"check_admission\": {\n        \"ADMITTED_RULE\": {\n            \"condition\": lambda power: power in [\"strength\", \"fly\", \"immortality\"],\n            \"condition_parameters\": {\"power\": \"input.super_power\"}, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": True},\n        },\n        \"DEFAULT_RULE\": {\n            \"condition\": None,\n            \"condition_parameters\": None, \n            \"action\": set_admission,\n            \"action_parameters\": {\"value\": False},\n        },\n    }\n}\n

    With modified data like:

    input_data = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n

    By default we will get a KeyError exception during the execution of the apply_rules() method because of power vs super_power.

    "},{"location":"parameters/#ignore","title":"Ignore","text":"

    You can change the by default raising behavior of the parameter's parsing.

    Two ways are possible:

    "},{"location":"parameters/#configuration-level","title":"Configuration level","text":"

    You just have to add the following key somewhere in your configuration:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules: \n  - my_folder.actions\n\nparsing_error_strategy: ignore  # (1)\n
    1. parsing_error_strategy has two possible values: raise and ignore.

    It will affect all the parameters.

    "},{"location":"parameters/#parameter-level","title":"Parameter level","text":"

    Quick Sum Up

    You can also handle more precisely that aspect at parameter's level:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power?  # (1)\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules: \n  - my_folder.actions\n
    1. Have you noticed the '?' ? If there is a KeyError when reading, power will be set to None rather than raising the exception.

    Info

    You can enforce raising exceptions at parameter's level with !.

    power: input.super_power!\n
    "},{"location":"parameters/#default-value-parameter-level","title":"Default value (parameter level)","text":"

    Finally, you can set a default value at parameter's level. This value will be used if there is an exception during parsing:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power?no_power  # (1)\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules: \n  - my_folder.actions\n
    1. If there is an exception during parsing, power will be set to \"no_power\".

    Good to know

    Parameter's level is overriding configuration level.

    "},{"location":"rule_sets/","title":"Rule sets","text":"

    Rule sets are a convenient way to separate your business rules into different collections.

    Doing so increases the rules' maintainability because of a better organization and fully uncoupled rules.

    Tip

    Rule sets are very usefull when you have a lot of rules.

    Info

    Most of the time, you won't need to handle different rule sets and will only use the default one: default_rule_set.

    The good news is that different rule sets can be used seamlessly with the same rules engine instance

    Let's take the following example:

    Based on that example, imagine that you need to add some rules about something totally different than the superhero school. Let's say rules for a dinosaur school.

    "},{"location":"rule_sets/#configuration","title":"Configuration","text":"

    Update your configuration by adding a new rule set: dinosaur_school_set

    ---\nrules:\n  superhero_school_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n  dinosaur_school_set:  # (1)\n    food_habit:\n      HERBIVOROUS:\n        condition: not(IS_EATING_MEAT)\n        action: send_mail_to_cook\n        action_parameters:\n          meal: \"plant\"\n      CARNIVOROUS:\n        condition: null\n        action: send_mail_to_cook\n        action_parameters:\n          meal: \"meat\"\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n  IS_EATING_MEAT:  # (2)\n    description: \"Is dinosaur eating meat?\"\n    validation_function: is_eating_meat\n    condition_parameters:\n      power: input.diet.regular_food\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules:\n  - my_folder.actions\n
    1. Add your new set of rules under the rules key
    2. Regular condition configuration, nothing new here

    Good to know

    You can define your rule sets into different YAML files (under the rules key in each).

    "},{"location":"rule_sets/#usage","title":"Usage","text":"

    Now that your rule sets are defined (and assuming that your condition and action functions are implemented in the right modules), you can easily use them:

    from arta import RulesEngine\n\ninput_data_1 = {\n    \"id\": 1,\n    \"name\": \"Superman\",\n    \"civilian_name\": \"Clark Kent\",\n    \"age\": None,\n    \"city\": \"Metropolis\",\n    \"language\": \"french\",\n    \"super_power\": \"fly\",\n    \"favorite_meal\": \"Spinach\",\n    \"secret_weakness\": \"Kryptonite\",\n    \"weapons\": [],\n}\n\ninput_data_2 = {\n    \"id\": 1,\n    \"name\": \"Diplodocus\",\n    \"age\": 152000000,\n    \"length\": 31,\n    \"area\": \"north_america\",\n    \"diet\": {\n        \"regular_food\": \"plants\",\n    },\n}\n\neng = RulesEngine(config_path=\"path/to/conf/dir\")\n\nsuperhero_result = eng.apply_rules(input_data_1, rule_set=\"superhero_school_set\")  # (1)\n\ndinosaur_result = eng.apply_rules(input_data_2, rule_set=\"dinosaur_school_set\")\n
    1. Select the rule set that you want to use when applying rules on your input data.

    Good to know

    Input data can be different or the same among the rule sets. It depends on the use case.

    "},{"location":"rule_sets/#object-oriented-model","title":"Object-Oriented Model","text":"
    classDiagram\n    rule_set \"1\" -- \"1..*\" rule_group\n    rule_group \"1\" -- \"1..*\" rule\n    rule \"1..*\" -- \"0..*\" condition\n    rule \"1..*\" -- \"1\" action
    "},{"location":"special_conditions/","title":"Special conditions","text":""},{"location":"special_conditions/#custom-condition","title":"Custom condition","text":"

    Custom conditions are user-defined conditions.

    A custom condition will impact the atomic evaluation of each conditions (i.e., condition ids).

    Vocabulary

    To be more precise, a condition expression is something like:

    CONDITION_1 and CONDITION_2\n

    In that example, the condition expression is made of 2 conditions whose condition ids are:

    With the built-in condition (also named standard condition), condition ids map to validation functions and condition parameters but we can change that with a brand new custom condition.

    A custom condition example:

    my_condition: NAME_JOHN and AGE_42\n

    Remember

    condition ids have to be in CAPITAL LETTERS.

    Imagine you want it to be interpreted as (pseudo-code):

    if input.name == \"john\" and input.age == \"42\":\n    # Do something\n    ...\n

    With the custom conditions it's quite simple to implement.

    Why using a custom condition?

    The main goal is to simplify handling of recurrent conditions (e.i., \"recurrent\" meaning very similar conditions).

    "},{"location":"special_conditions/#class-implementation","title":"Class implementation","text":"

    First, create a class inheriting from BaseCondtion and implement the verify() method as you want/need:

    from typing import Any\n\nfrom arta.condition import BaseCondition\nfrom arta.utils import ParsingErrorStrategy\n\n\nclass MyCondition(BaseCondition):\n    def verify(\n        self,\n        input_data: dict[str, Any],\n        parsing_error_strategy: ParsingErrorStrategy,\n        **kwargs: Any\n    ) -> bool:\n\n        field, value = tuple(self.condition_id.split(\"_\"))\n\n        return input_data[field.lower()] == value.lower()\n

    self.condition_id

    self.condition_id will be NAME_JOHN for the first condition and AGE_42 for the second.

    Good to know

    The parsing_error_strategy can be used by the developer to adapt exception handling behavior. Possible values:

    ParsingErrorStrategy.RAISE\nParsingErrorStrategy.IGNORE\nParsingErrorStrategy.DEFAULT_VALUE\n
    "},{"location":"special_conditions/#configuration","title":"Configuration","text":"

    Last thing to do is to add your new custom condition in the configuration:

    ---\nrules:\n  default_rule_set:\n    check_admission:\n      ADMITTED_RULE:\n        condition: HAS_SCHOOL_AUTHORIZED_POWER\n        my_condition: NAME_JOHN and AGE_42  # (1)\n        action: set_admission\n        action_parameters:\n          value: true\n      DEFAULT_RULE:\n        condition: null\n        action: set_admission\n        action_parameters:\n          value: false\n\nconditions:\n  HAS_SCHOOL_AUTHORIZED_POWER:\n    description: \"Does applicant have a school authorized power?\"\n    validation_function: has_authorized_super_power\n    condition_parameters:\n      power: input.super_power\n\nconditions_source_modules:\n  - my_folder.conditions\nactions_source_modules: \n  - my_folder.actions\n\ncustom_classes_source_modules:\n  - dir.to.my_module  # (2)\ncondition_factory_mapping:\n  my_condition: MyCondition # (3)\n
    1. Order is important, here it will evaluate condition then my_condition. Order is arbitrary.
    2. List of the modules containing custom classes
    3. Mapping between condition keys (my_condition) and custom classes (MyCondition)
    "},{"location":"special_conditions/#class-diagram","title":"Class diagram","text":"

    It is based on the following strategy pattern:

    classDiagram\n    note for MyCondition \"This is a custom condition class\"\n    RulesEngine \"1\" -- \"1..*\" Rule\n    Rule \"1..*\" -- \"0..*\" BaseCondition\n    BaseCondition <|-- StandardCondition\n    BaseCondition <|-- SimpleCondition\n    BaseCondition <|-- MyCondition\n    class RulesEngine{\n        +rules\n        +apply_rules()\n    }\n    class Rule {\n        #set_id\n        #group_id\n        #rule_id\n        #condition_exprs\n        #action\n        #action_parameters\n        +apply()\n    }\n    class BaseCondition {\n        <<abstract>>\n        #condition_id\n        #description\n        #validation_function\n        #validation_function_parameters\n        +verify()\n    }

    Good to know

    The class StandardCondition is the built-in implementation of a condition.

    "},{"location":"why/","title":"Why use Arta?","text":"

    There is one main reason for using Arta and it was the main goal of its development:

    Increase business rules maintainability.

    In other words, facilitate rules handling in a python app.

    "},{"location":"why/#before-arta","title":"Before Arta","text":"

    Rules in code can rapidly become a headache, kind of spaghetti dish of if, elif and else (or even match/case since Python 3.10).

    "},{"location":"why/#after-arta","title":"After Arta","text":"

    Arta increases rules maintainability:

    Improve collaboration

    Reading python code vs reading YAML.

    "},{"location":"assets/js/tarteaucitron/","title":"Index","text":""},{"location":"assets/js/tarteaucitron/#tarteaucitronjs","title":"tarteaucitron.js","text":"

    Comply to the european cookie law is simple with the french tarte au citron.

    "},{"location":"assets/js/tarteaucitron/#what-is-this-script","title":"What is this script?","text":"

    The european cookie law regulates the management of cookies and you should ask your visitors their consent before exposing them to third party services.

    Clearly this script will: - Disable all services by default, - Display a banner on the first page view and a small one on other pages, - Display a panel to allow or deny each services one by one, - Activate services on the second page view if not denied, - Store the consent in a cookie for 365 days.

    Bonus: - Load service when user click on Allow (without reload of the page), - Incorporate a fallback system (display a link instead of social button and a static banner instead of advertising).

    "},{"location":"assets/js/tarteaucitron/#supported-services","title":"Supported services","text":""},{"location":"assets/js/tarteaucitron/#visitors-outside-the-eu","title":"Visitors outside the EU","text":"

    In PHP for example, you can bypass all the script by setting this var tarteaucitron.user.bypass = true; if the visitor is not in the EU.

    "},{"location":"assets/js/tarteaucitron/#tested-on","title":"Tested on","text":""},{"location":"assets/js/tarteaucitron/#installation-guide","title":"Installation guide","text":"

    Visit opt-out.ferank.eu

    "}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 4b6e771..9f2bc91 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,57 +2,57 @@ https://maif.github.io/arta/a_simple_example/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/api_reference/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/glossary/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/home/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/how_to/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/installation/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/parameters/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/rule_sets/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/special_conditions/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/why/ - 2024-06-06 + 2024-06-10 daily https://maif.github.io/arta/assets/js/tarteaucitron/ - 2024-06-06 + 2024-06-10 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 6a98a88..4056830 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ