"""Validator base class.""" import re class BaseValidator: """Validator base class. It contains methods to validate SQL identifies. We have to introduce custom validation, because we can't bind db identifiers (db, schema, column names, etc.) within cursor.execute() method. """ def is_valid_identifier(self, identifier): """Check if passed identifier is valid. Args: identifier (str): An SQL identifier (e.g. schema or table name). Returns: bool: True if valid, False otherwise. """ # at least one non-digit char at the beginning if re.match('^[a-zA-Z_]+[a-zA-Z0-9_]*$', identifier): return True if re.match('^\"[^\"]*\"$', identifier): return True return False def format_identifiers(self, sql_template, params): """Format SQL template with all the identifiers. Args: sql_template (str): SQL template to format. params (dict): Params (identifiers and non-identifiers). Returns: tuple (sql_template, non_identifier_params): sql_template is a template which was formatted with identifiers, non_identifier_params is a dict with a rest of params, which are not identifiers and could be bound within execute(). """ id_pattern = re.compile('%\(([a-zA-Z_]+[a-zA-Z0-9_]*)\)i') ids = {k: params.pop(k) for k in list(set(id_pattern.findall( sql_template)))} for k, v in ids.items(): assert self.is_valid_identifier(v) sql_template = sql_template.replace('%({})i'.format(k), v) non_identifier_params = params return sql_template, non_identifier_params