added generic rule generation
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
|
||||
from abc import ABCMeta
|
||||
from typing import Any, _SpecialForm
|
||||
|
||||
def check_input(variable:Any, expected_type:type, alt_types:list[type]=[],
|
||||
@ -70,14 +71,18 @@ def valid_string(variable:str, valid_chars:str, min_length:int=1)->None:
|
||||
|
||||
def valid_dict(variable:dict[Any, Any], key_type:type, value_type:type,
|
||||
required_keys:list[Any]=[], optional_keys:list[Any]=[],
|
||||
strict:bool=True)->None:
|
||||
strict:bool=True, min_length:int=1)->None:
|
||||
check_input(variable, dict)
|
||||
check_input(key_type, type, alt_types=[_SpecialForm])
|
||||
check_input(value_type, type, alt_types=[_SpecialForm])
|
||||
check_input(key_type, type, alt_types=[_SpecialForm, ABCMeta])
|
||||
check_input(value_type, type, alt_types=[_SpecialForm, ABCMeta])
|
||||
check_input(required_keys, list)
|
||||
check_input(optional_keys, list)
|
||||
check_input(strict, bool)
|
||||
|
||||
if len(variable) < min_length:
|
||||
raise ValueError(f"Dictionary '{variable}' is below minimum length of "
|
||||
f"{min_length}")
|
||||
|
||||
for k, v in variable.items():
|
||||
if key_type != Any and not isinstance(k, key_type):
|
||||
raise TypeError(f"Key {k} had unexpected type '{type(k)}' "
|
||||
@ -96,3 +101,12 @@ def valid_dict(variable:dict[Any, Any], key_type:type, value_type:type,
|
||||
if k not in required_keys and k not in optional_keys:
|
||||
raise ValueError(f"Unexpected key '{k}' should not be present "
|
||||
f"in dict '{variable}'")
|
||||
|
||||
def valid_list(variable:list[Any], entry_type:type,
|
||||
alt_types:list[type]=[], min_length:int=1)->None:
|
||||
check_input(variable, list)
|
||||
if len(variable) < min_length:
|
||||
raise ValueError(f"List '{variable}' is too short. Should be at least "
|
||||
f"of length {min_length}")
|
||||
for entry in variable:
|
||||
check_input(entry, entry_type, alt_types=alt_types)
|
||||
|
78
core/functionality.py
Normal file
78
core/functionality.py
Normal file
@ -0,0 +1,78 @@
|
||||
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
from typing import Union
|
||||
from random import SystemRandom
|
||||
|
||||
from core.meow import BasePattern, BaseRecipe, BaseRule
|
||||
from core.correctness.validation import check_input, valid_dict, valid_list
|
||||
from core.correctness.vars import CHAR_LOWERCASE, CHAR_UPPERCASE
|
||||
from patterns import *
|
||||
from recipes import *
|
||||
from rules import *
|
||||
|
||||
def check_pattern_dict(patterns, min_length=1):
|
||||
valid_dict(patterns, str, BasePattern, strict=False, min_length=min_length)
|
||||
for k, v in patterns.items():
|
||||
if k != v.name:
|
||||
raise KeyError(f"Key '{k}' indexes unexpected Pattern '{v.name}' "
|
||||
"Pattern dictionaries must be keyed with the name of the "
|
||||
"Pattern.")
|
||||
|
||||
def check_recipe_dict(recipes, min_length=1):
|
||||
valid_dict(recipes, str, BaseRecipe, strict=False, min_length=min_length)
|
||||
for k, v in recipes.items():
|
||||
if k != v.name:
|
||||
raise KeyError(f"Key '{k}' indexes unexpected Recipe '{v.name}' "
|
||||
"Recipe dictionaries must be keyed with the name of the "
|
||||
"Recipe.")
|
||||
|
||||
def generate_id(prefix:str="", length:int=16, existing_ids:list[str]=[],
|
||||
charset:str=CHAR_UPPERCASE+CHAR_LOWERCASE, attempts:int=24):
|
||||
random_length = max(length - len(prefix), 0)
|
||||
for _ in range(attempts):
|
||||
id = prefix + ''.join(SystemRandom().choice(charset)
|
||||
for _ in range(random_length))
|
||||
if id not in existing_ids:
|
||||
return id
|
||||
raise ValueError(f"Could not generate ID unique from '{existing_ids}' "
|
||||
f"using values '{charset}' and length of '{length}'.")
|
||||
|
||||
def create_rules(patterns:Union[dict[str,BasePattern],list[BasePattern]],
|
||||
recipes:Union[dict[str,BaseRecipe],list[BaseRecipe]],
|
||||
new_rules:list[BaseRule]=[])->dict[str,BaseRule]:
|
||||
check_input(patterns, dict, alt_types=[list])
|
||||
check_input(recipes, dict, alt_types=[list])
|
||||
valid_list(new_rules, BaseRule, min_length=0)
|
||||
|
||||
if isinstance(patterns, list):
|
||||
valid_list(patterns, BasePattern, min_length=0)
|
||||
patterns = {pattern.name:pattern for pattern in patterns}
|
||||
else:
|
||||
check_pattern_dict(patterns, min_length=0)
|
||||
|
||||
if isinstance(recipes, list):
|
||||
valid_list(recipes, BaseRecipe, min_length=0)
|
||||
recipes = {recipe.name:recipe for recipe in recipes}
|
||||
else:
|
||||
check_recipe_dict(recipes, min_length=0)
|
||||
|
||||
rules = {}
|
||||
|
||||
all_rules ={(r.pattern_type, r.recipe_type):r for r in [r[1] \
|
||||
for r in inspect.getmembers(sys.modules["rules"], inspect.isclass) \
|
||||
if (issubclass(r[1], BaseRule))]}
|
||||
|
||||
for pattern in patterns.values():
|
||||
if pattern.recipe in recipes:
|
||||
key = (type(pattern).__name__,
|
||||
type(recipes[pattern.recipe]).__name__)
|
||||
if (key) in all_rules:
|
||||
rule = all_rules[key](
|
||||
generate_id(prefix="Rule_"),
|
||||
pattern,
|
||||
recipes[pattern.recipe]
|
||||
)
|
||||
rules[rule.name] = rule
|
||||
return rules
|
66
core/meow.py
66
core/meow.py
@ -2,6 +2,8 @@
|
||||
import core.correctness.vars
|
||||
import core.correctness.validation
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from typing import Any
|
||||
|
||||
class BaseRecipe:
|
||||
@ -20,21 +22,6 @@ class BaseRecipe:
|
||||
self._is_valid_requirements(requirements)
|
||||
self.requirements = requirements
|
||||
|
||||
def __init_subclass__(cls, **kwargs) -> None:
|
||||
if cls._is_valid_recipe == BaseRecipe._is_valid_recipe:
|
||||
raise NotImplementedError(
|
||||
f"Recipe '{cls.__name__}' has not implemented "
|
||||
"'_is_valid_recipe(self, recipe)' function.")
|
||||
if cls._is_valid_parameters == BaseRecipe._is_valid_parameters:
|
||||
raise NotImplementedError(
|
||||
f"Recipe '{cls.__name__}' has not implemented "
|
||||
"'_is_valid_parameters(self, parameters)' function.")
|
||||
if cls._is_valid_requirements == BaseRecipe._is_valid_requirements:
|
||||
raise NotImplementedError(
|
||||
f"Recipe '{cls.__name__}' has not implemented "
|
||||
"'_is_valid_requirements(self, requirements)' function.")
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BaseRecipe:
|
||||
raise TypeError("BaseRecipe may not be instantiated directly")
|
||||
@ -44,17 +31,20 @@ class BaseRecipe:
|
||||
core.correctness.validation.valid_string(
|
||||
name, core.correctness.vars.VALID_RECIPE_NAME_CHARS)
|
||||
|
||||
@abstractmethod
|
||||
def _is_valid_recipe(self, recipe:Any)->None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _is_valid_parameters(self, parameters:Any)->None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _is_valid_requirements(self, requirements:Any)->None:
|
||||
pass
|
||||
|
||||
|
||||
class BasePattern:
|
||||
class BasePattern(ABC):
|
||||
name:str
|
||||
recipe:str
|
||||
parameters:dict[str, Any]
|
||||
@ -70,21 +60,6 @@ class BasePattern:
|
||||
self._is_valid_output(outputs)
|
||||
self.outputs = outputs
|
||||
|
||||
def __init_subclass__(cls, **kwargs) -> None:
|
||||
if cls._is_valid_recipe == BasePattern._is_valid_recipe:
|
||||
raise NotImplementedError(
|
||||
f"Pattern '{cls.__name__}' has not implemented "
|
||||
"'_is_valid_recipe(self, recipe)' function.")
|
||||
if cls._is_valid_parameters == BasePattern._is_valid_parameters:
|
||||
raise NotImplementedError(
|
||||
f"Pattern '{cls.__name__}' has not implemented "
|
||||
"'_is_valid_parameters(self, parameters)' function.")
|
||||
if cls._is_valid_output == BasePattern._is_valid_output:
|
||||
raise NotImplementedError(
|
||||
f"Pattern '{cls.__name__}' has not implemented "
|
||||
"'_is_valid_output(self, outputs)' function.")
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BasePattern:
|
||||
raise TypeError("BasePattern may not be instantiated directly")
|
||||
@ -94,20 +69,25 @@ class BasePattern:
|
||||
core.correctness.validation.valid_string(
|
||||
name, core.correctness.vars.VALID_PATTERN_NAME_CHARS)
|
||||
|
||||
@abstractmethod
|
||||
def _is_valid_recipe(self, recipe:Any)->None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _is_valid_parameters(self, parameters:Any)->None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _is_valid_output(self, outputs:Any)->None:
|
||||
pass
|
||||
|
||||
|
||||
class BaseRule:
|
||||
class BaseRule(ABC):
|
||||
name:str
|
||||
pattern:BasePattern
|
||||
recipe:BaseRecipe
|
||||
pattern_type:str=""
|
||||
recipe_type:str=""
|
||||
def __init__(self, name:str, pattern:BasePattern, recipe:BaseRecipe):
|
||||
self._is_valid_name(name)
|
||||
self.name = name
|
||||
@ -115,29 +95,29 @@ class BaseRule:
|
||||
self.pattern = pattern
|
||||
self._is_valid_recipe(recipe)
|
||||
self.recipe = recipe
|
||||
self.__check_types_set()
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BaseRule:
|
||||
raise TypeError("BaseRule may not be instantiated directly")
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init_subclass__(cls, **kwargs) -> None:
|
||||
if cls._is_valid_pattern == BaseRule._is_valid_pattern:
|
||||
raise NotImplementedError(
|
||||
f"Rule '{cls.__name__}' has not implemented "
|
||||
"'_is_valid_pattern(self, pattern)' function.")
|
||||
if cls._is_valid_recipe == BaseRule._is_valid_recipe:
|
||||
raise NotImplementedError(
|
||||
f"Pattern '{cls.__name__}' has not implemented "
|
||||
"'_is_valid_recipe(self, recipe)' function.")
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
def _is_valid_name(self, name:str)->None:
|
||||
core.correctness.validation.valid_string(
|
||||
name, core.correctness.vars.VALID_RULE_NAME_CHARS)
|
||||
|
||||
@abstractmethod
|
||||
def _is_valid_pattern(self, pattern:Any)->None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _is_valid_recipe(self, recipe:Any)->None:
|
||||
pass
|
||||
|
||||
def __check_types_set(self)->None:
|
||||
if self.pattern_type == "":
|
||||
raise AttributeError(f"Rule Class '{self.__class__.__name__}' "
|
||||
"does not set a pattern_type.")
|
||||
if self.recipe_type == "":
|
||||
raise AttributeError(f"Rule Class '{self.__class__.__name__}' "
|
||||
"does not set a recipe_type.")
|
||||
|
Reference in New Issue
Block a user