def __init__(self, derive: InstrumentedAttribute, from_parent: any): super(Copy, self).__init__(derive) if isinstance(from_parent, str): names = from_parent.split('.') self._from_parent_role = names[0] self._from_column = names[1] elif isinstance(from_parent, InstrumentedAttribute): self._from_column = from_parent.key table_class = from_parent.class_ parent_class_name = self.get_class_name(table_class) pass attrs = self._derive.parent.attrs found_attr = None for each_attr in attrs: if isinstance(each_attr, RelationshipProperty): each_parent_class_nodal_name = each_attr.entity.class_ each_parent_class_name = self.get_class_name( each_parent_class_nodal_name) if each_parent_class_name == parent_class_name: if found_attr is not None: raise Exception( "TODO / copy - disambiguate relationship") found_attr = each_attr if found_attr is None: raise Exception("Invalid 'as_sum_of' - not a reference to: " + self.table + " in " + self.__str__()) else: self._from_parent_role = found_attr.key else: pass rb = RuleBank() rb.deposit_rule(self)
def __init__(self, derive: str, as_sum_of: str, where: str): super(Sum, self).__init__(derive) self._as_sum_of = as_sum_of # could probably super-ize parent accessor self._from_parent_role = self._as_sum_of.split(".")[0] self._where = where rb = RuleBank() rb.deposit_rule(self)
def __init__(self, derive: str, as_count_of: str, where: str): super(Count, self).__init__(derive) self._as_count_of = as_count_of # could probably super-ize parent accessor self._from_parent_role = as_count_of self._where = where rb = RuleBank() rb.deposit_rule(self)
def __init__(self, derive: str, from_parent: str): super(Copy, self).__init__(derive) names = from_parent.split('.') self._from_parent_role = names[0] self._from_column = names[1] rb = RuleBank() rb.deposit_rule(self)
def validate(a_session: session, engine: Engine): """ Determine formula execution order based on "row.xx" references, (or raise exception if cycles detected). """ list_rules = "\n\nValidate Rule Bank" rules_bank = RuleBank() for each_key in rules_bank._tables: validate_formula_dependencies(class_name=each_key) list_rules += rules_bank.__str__() print(list_rules) return True
def update_referenced_parent_attributes(self, dependencies: list): """ Used by Formulas and constraints log their dependence on parent attributes This sets RuleBank.TableRules[mapped_class].referring_children dependencies is a list But, can't do this now, because meta_contains_role_name = False So, do it on the fly in logic_row (which is an ugh) """ meta_contains_role_name = False if meta_contains_role_name is False: return else: meta_data = rule_bank_withdraw.get_meta_data() child_meta = meta_data.tables[self.table] parent_role_name = dependencies[1] foreign_keys = child_meta.foreign_keys for each_foreign_key in foreign_keys: # eg, OrderDetail has OrderHeader, Product each_parent_class_name = each_foreign_key.name each_parent_role_name = each_foreign_key.key if parent_role_name == each_parent_role_name: # eg, OrderHeader rule_bank = RuleBank() if each_parent_class_name not in rule_bank._tables: self._tables[rule_bank] = TableRules() table_rules = self._tables[rule_bank] if table_rules.referring_children is None: table_rules.referring_children = {} if parent_role_name not in table_rules.referring_children: table_rules.referring_children[parent_role_name] = [] table_rules.referring_children.append(dependencies[2]) engine_logger.debug( prt("child parent dependency: " + dependencies[1])) break
def __init__(self, row: base, old_row: base, ins_upd_dlt: str, nest_level: int, a_session: session, row_cache: object): self.session = a_session self.row = row # type(base) """ mapped row """ self.old_row = old_row """ old mapped row """ self.ins_upd_dlt = ins_upd_dlt self.ins_upd_dlt_initial = ins_upd_dlt # order inserted, then adjusted self.nest_level = nest_level self.reason = "?" # set by insert, update and delete """ if starts with cascade, triggers cascade processing """ self.row_cache = row_cache if row_cache is not None: # eg, for debug as in upd_order_shipped test row_cache.add(logic_row=self) rb = RuleBank() self.rb = rb self.session = rb._session self.engine = rb._engine self.some_base = declarative_base() self.name = type(self.row).__name__ self.table_meta = row.metadata.tables[type(self.row).__name__] self.inspector = Inspector.from_engine(self.engine)
def aggregate_rules(child_logic_row: LogicRow) -> dict: """returns dict(<parent_role_name>, sum/count_rules[] for given child_table_name This requires we **invert** the RuleBank, to find sums that reference child_table_name, grouped by parent_role e.g., for child_logic_row "Order", we return ["Order", (Customer.balance, Customer.order_count...) ["Employee, (Employee.order_count)] """ result_role_rules_list = {} # dict of RoleRules child_mapper = object_mapper(child_logic_row.row) rule_bank = RuleBank() relationships = child_mapper.relationships for each_relationship in relationships: # eg, order has parents cust & emp, child orderdetail if each_relationship.direction == sqlalchemy.orm.interfaces.MANYTOONE: # cust, emp child_role_name = each_relationship.back_populates # eg, OrderList if child_role_name is None: child_role_name = child_mapper.class_.__name__ # default TODO design review parent_role_name = each_relationship.key # eg, Customer TODO design review parent_class_name = each_relationship.entity.class_.__name__ if parent_class_name in rule_bank._tables: parent_rules = rule_bank._tables[parent_class_name].rules for each_parent_rule in parent_rules: # (.. bal = sum(OrderList.amount) ) if isinstance(each_parent_rule, (Sum, Count)): if each_parent_rule._child_role_name == child_role_name: if parent_role_name not in result_role_rules_list: result_role_rules_list[parent_role_name] = [] result_role_rules_list[parent_role_name].append( each_parent_rule) each_parent_rule._parent_role_name = parent_role_name return result_role_rules_list
def get_formula_rules(class_name: str) -> list: """withdraw rules of designated a_class """ rule_bank = RuleBank() rules_list = [] role_rules_list = {} # dict of RoleRules for each_rule in rule_bank._tables[class_name].rules: if isinstance(each_rule, Formula): rules_list.append(each_rule) return rules_list
def rules_of_class(a_table_name: str, a_class: (Formula, Constraint)) -> list: """withdraw rules of designated a_class """ rule_bank = RuleBank() rules_list = [] role_rules_list = {} # dict of RoleRules for each_rule in rule_bank._tables[a_table_name]: if isinstance(each_rule, a_class): rules_list.append(each_rule) return rules_list
def __init__(self, derive: InstrumentedAttribute, as_sum_of: any, where: any): super(Sum, self).__init__(derive) self._as_sum_of = as_sum_of # could probably super-ize parent accessor self._where = where if isinstance(as_sum_of, str): self._child_role_name = self._as_sum_of.split(".")[ 0] # child role retrieves children self._child_summed_field = self._as_sum_of.split(".")[1] elif isinstance(as_sum_of, InstrumentedAttribute): self._child_summed_field = as_sum_of.key attrs = as_sum_of.parent.attrs found_attr = None for each_attr in attrs: if isinstance(each_attr, RelationshipProperty): pass parent_class_nodal_name = each_attr.entity.class_ parent_class_name = self.get_class_name( parent_class_nodal_name) if parent_class_name == self.table: if found_attr is not None: raise Exception("TODO - disambiguate relationship") found_attr = each_attr if found_attr is None: raise Exception("Invalid 'as_sum_of' - not a reference to: " + self.table + " in " + self.__str__()) else: self._child_role_name = found_attr.back_populates else: raise Exception( "as_sum_of must be either string, or <mapped-class.column>: " + str(as_sum_of)) if where is None: self._where_cond = lambda row: True elif isinstance(where, str): self._where_cond = lambda row: eval(where) elif isinstance(where, Callable): self._where_cond = where else: raise Exception("'where' must be string, or lambda: " + self.__str__()) rb = RuleBank() rb.deposit_rule(self)
def copy_rules(a_table_name: str) -> CopyRulesForTable: """dict(<role_name>, copy_rules[] """ rule_bank = RuleBank() role_rules_list = {} # dict of RoleRules for each_rule in rule_bank._tables[a_table_name]: if isinstance(each_rule, Copy): role_name = each_rule._from_parent_role if role_name not in role_rules_list: role_rules_list[role_name] = [] role_rules_list[role_name].append(each_rule) return role_rules_list
def aggregate_rules(a_table_name: str) -> dict: """dict(<role_name>, sum/count_rules[] """ rule_bank = RuleBank() role_rules_list = {} # dict of RoleRules for each_rule in rule_bank._tables[a_table_name]: if isinstance(each_rule, (Sum, Count)): role_name = each_rule._from_parent_role if role_name not in role_rules_list: role_rules_list[role_name] = [] role_rules_list[role_name].append(each_rule) return role_rules_list
def generic_rules_of_class(a_class: (Formula, Constraint, EarlyRowEvent)) -> list: """withdraw rules of the "*" (any) class """ rule_bank = RuleBank() rules_list = [] role_rules_list = {} # dict of RoleRules if "*" in rule_bank._tables: for each_rule in rule_bank._tables["*"].rules: if isinstance(each_rule, a_class): rules_list.append(each_rule) return rules_list
def setup(a_session: session, an_engine: Engine): rules_bank = RuleBank() rules_bank._session = a_session event.listen(a_session, "before_flush", before_flush) rules_bank._tables = {} rules_bank._at = datetime.now() rules_bank._engine = an_engine rules_bank._rb_base = declarative_base # FIXME good grief, not appearing, no error return
def rules_of_class( logic_row: LogicRow, a_class: (Formula, Constraint, EarlyRowEvent)) -> list: """withdraw rules of designated a_class """ rule_bank = RuleBank() rules_list = [] role_rules_list = {} # dict of RoleRules if logic_row.name in rule_bank._tables: for each_rule in rule_bank._tables[logic_row.name].rules: if isinstance(each_rule, a_class): rules_list.append(each_rule) return rules_list
def __init__( self, validate: object, error_msg: str, calling: Callable = None, as_condition: object = None): # str or lambda boolean expression super(Constraint, self).__init__(validate) # self.table = validate # setter finds object self._error_msg = error_msg self._as_condition = as_condition self._calling = calling if calling is None and as_condition is None: raise Exception( f'Constraint {str} requires calling or as_expression') if calling is not None and as_condition is not None: raise Exception( f'Constraint {str} either calling or as_expression') if calling is not None: self._function = calling elif isinstance(as_condition, str): self._as_condition = lambda row: eval(as_condition) ll = RuleBank() ll.deposit_rule(self)
def __init__(self, derive: InstrumentedAttribute, as_exp: str = None, # for very short expressions as_expression: Callable = None, # short, with type checking calling: Callable = None # complex formula ): """ Specify rep * as_exp - string (for very short expressions - price * quantity) * ex_expression - lambda (for type checking) * calling - function (for more complex formula, with old_row) """ super(Formula, self).__init__(derive) self._as_exp = as_exp self._as_expression = as_expression self._function = calling self._as_exp_lambda = None # we exec this, or _function valid_count = 0 if as_exp is not None: self._as_exp_lambda = lambda row: eval(as_exp) valid_count += 1 if as_expression is not None: self._as_exp_lambda = as_expression valid_count += 1 if calling is not None: valid_count += 1 if valid_count != 1: raise Exception(f'Formula requires one of as_exp, as_expression or calling') self._dependencies = [] text = self.get_rule_text() self.parse_dependencies(rule_text=text) self._exec_order = -1 # will be computed in rule_bank_setup (all rules loaded) rb = RuleBank() rb.deposit_rule(self)
def __init__(self, row: base, old_row: base, ins_upd_dlt: str, nest_level: int): self.row = row self.old_row = old_row self.ins_upd_dlt = ins_upd_dlt self.nest_level = nest_level rb = RuleBank() self.rb = rb self.session = rb._session self.engine = rb._engine self.some_base = declarative_base() self.name = type(self.row).__name__ self.table_meta = row.metadata.tables[type(self.row).__name__] self.inspector = Inspector.from_engine(self.engine)
def setup(a_session: session, an_engine: Engine ): # TODO major - ensure compatible with fab, flask etc """ Initialize the RuleBank """ rules_bank = RuleBank() rules_bank._session = a_session event.listen(a_session, "before_flush", before_flush) rules_bank._tables = {} rules_bank._at = datetime.now() rules_bank._engine = an_engine rules_bank._metadata = MetaData(bind=an_engine, reflect=True) from sqlalchemy.ext.declarative import declarative_base rules_bank._base = declarative_base() return
def get_referring_children(parent_logic_row: LogicRow) -> dict: """ return RulesBank[class_name].referring_children (create if None) referring_children is <parent_role_name>, parent_attribute_list() """ rule_bank = RuleBank() table_rules = rule_bank._tables[parent_logic_row.name] result = table_rules.referring_children if result is not None: return result else: # sigh, best to have build this in rule_bank_setup, but unable to get mapper # FIXME design is this threadsafe? table_rules.referring_children = {} parent_mapper = object_mapper(parent_logic_row.row) parent_relationships = parent_mapper.relationships for each_parent_relationship in parent_relationships: # eg, order has parents cust & emp, child orderdetail if each_parent_relationship.direction == sqlalchemy.orm.interfaces.ONETOMANY: # cust, emp parent_role_name = each_parent_relationship.back_populates # eg, OrderList table_rules.referring_children[parent_role_name] = [] child_role_name = each_parent_relationship.key child_class_name = get_child_class_name( each_parent_relationship) # eg, OrderDetail child_table_rules = rule_bank._tables[child_class_name].rules search_for_rew_parent = "row." + parent_role_name if child_table_rules is not None: for each_rule in child_table_rules: if isinstance( each_rule, (Formula, Constraint)): # eg, OrderDetail.ShippedDate rule_text = each_rule.get_rule_text( ) # eg, row.OrderHeader.ShippedDate rule_words = rule_text.split() for each_word in rule_words: if each_word.startswith(search_for_rew_parent): rule_terms = each_word.split(".") # if parent_role_name not in table_rules.referring_children: # table_rules.referring_children[parent_role_name] = () table_rules.referring_children[ parent_role_name].append(rule_terms[2]) return table_rules.referring_children
def __init__(self, validate: str, calling): super(Constraint, self).__init__(validate) # self.table = validate # setter finds object self._function = calling ll = RuleBank() ll.deposit_rule(self)
def __init__(self, derive: str, calling: Callable): super(Formula, self).__init__(derive) self._function = calling rb = RuleBank() rb.deposit_rule(self)
def get_meta_data(): rule_bank = RuleBank() return rule_bank._metadata
def get_session(): rule_bank = RuleBank() return rule_bank._session
def __init__(self, on_class: object, calling: Callable = None): super(AbstractRowEvent, self).__init__(on_class) self._function = calling ll = RuleBank() ll.deposit_rule(self)
basedir = os.path.dirname(basedir) conn_string = "sqlite:///" + os.path.join(basedir, "nw-app/nw.db") # e.g. 'sqlite:////Users/val/python/vsc/logic-explore/nw-app/nw.db' engine = sqlalchemy.create_engine(conn_string, echo=False) # sqlalchemy sqls... # Create a session session_maker = sqlalchemy.orm.sessionmaker() session_maker.configure(bind=engine) session = session_maker() do_logic = True rule_list = None db = None if do_logic: rule_bank = RuleBank() rule_bank_setup.setup(session, engine) from .nw_rules_bank import NwLogic rule_bank = RuleBank( ) # FIXME - unclear why this returns the *correct* singleton, vs 2 lines above copies = rule_bank_withdraw.copy_rules("OrderDetail") aggregates = rule_bank_withdraw.aggregate_rules("Customer") constraints = rule_bank_withdraw.rules_of_class("Customer", Constraint) formulas = rule_bank_withdraw.rules_of_class("OrderDetail", Formula) print("\n\nlogic loaded:\n" + str(rule_bank)) # target, modifier, function event.listen(session, "before_commit", nw_before_commit) event.listen(session, "before_flush", nw_before_flush) # event.listen(Order.ShippedDate, "set", order_modified)