import ast from dataclasses import dataclass from typing import Any import django.db.backends.base.features as BaseFeatureModule from django.utils.functional import cached_property from django.db.backends.base.features import BaseDatabaseFeatures from django.db.backends.mysql.features import DatabaseFeatures as MySqlFeatures from django.db.backends.oracle.features import DatabaseFeatures as OracleFeatures from django.db.backends.postgresql.features import DatabaseFeatures as PostgresFeatures import django.db.backends.sqlite3.base # to avoid partial import from django.db.backends.sqlite3.features import DatabaseFeatures as SqliteFeatures BACKENDS = { "postgres": PostgresFeatures, "sqlite": SqliteFeatures, "oracle": OracleFeatures, "mysql": MySqlFeatures, } MAYBE = "maybe" NOT_FLAGS = { "minimum_database_version", } def is_supported(value): match value: case int(): return value > 0 case bool(): return value case property(): return MAYBE case _: return bool(value) # consider cached_property @dataclass class DatabaseFeatureFlag(): attribute_name: str description: str value: Any min_version: tuple[int, ...] # details: ... def __get__(self): return self.value def parse_ast(): with open(BaseFeatureModule.__file__, 'r') as module_file: module_text = module_file.read() module = ast.parse(module_text) klass = [elem for elem in module.body if isinstance(elem, ast.ClassDef)][0] return klass def get_comment_for_feature_flag(class_node, feature_flag): """If a docstring exists underneath the feature flag, return the value.""" # searches every time... found = False for body_node in class_node.body: if found: if isinstance(body_node, ast.Expr): return body_node.value.value if isinstance(body_node, ast.Assign): if body_node.targets[0].id == feature_flag: if hasattr(body_node.value, "value"): found = True def report_features(class_node): for feature_flag, default_value in vars(BaseDatabaseFeatures).items(): if ( not ( isinstance(default_value, (list, set, tuple, bool, int, property)) or default_value is None ) or feature_flag.startswith("__") or "test" in feature_flag or feature_flag in NOT_FLAGS ): # skipping methods for now continue print(feature_flag, ": ", get_comment_for_feature_flag(class_node, feature_flag)) for name, backend in BACKENDS.items(): got = getattr(backend, feature_flag) print("\t", f"{name}: ", is_supported(got)) if __name__ == "__main__": class_node = parse_ast() report_features(class_node=class_node)