from abc import ABC, abstractmethod from flask import current_app, g, request from .constants import ReplicaType, PolicyType from .utils import get_all_subclasses READONLY_METHODS = {'GET', 'HEAD'} class RoutingPolicy(ABC): """Base policy class for requests routing between replicated db's.""" type: PolicyType @classmethod def set_replica(cls): """Main method for choosing replica type and save it in g.""" view_function = current_app.view_functions.get(request.endpoint) # view function has 'set_replica' if it was used with set_replica decorator replica_type = getattr(view_function, 'set_replica', None) if replica_type is not None: if replica_type not in ReplicaType: raise ValueError(f"Wrong replica type {replica_type} was given.") else: replica_type = cls.get_replica() g._replica_type = replica_type @staticmethod @abstractmethod def get_replica() -> ReplicaType: """Return replica type for specific policy.""" pass class DefaultMasterPolicy(RoutingPolicy): """Policy for using master as default replica.""" type = PolicyType.DEFAULT_MASTER @staticmethod def get_replica() -> ReplicaType: return ReplicaType.MASTER class DefaultSlavePolicy(RoutingPolicy): """Policy for using slave as default replica.""" type = PolicyType.DEFAULT_SLAVE @staticmethod def get_replica() -> ReplicaType: return ReplicaType.SLAVE class RequestTypePolicy(RoutingPolicy): """Policy for using replica depending on request type: slave - for read only requests, master - for other. """ type = PolicyType.REQUEST_TYPE @staticmethod def get_replica() -> ReplicaType: return ReplicaType.SLAVE if request.method in READONLY_METHODS else ReplicaType.MASTER _POLICIES_ = {c.type: c for c in get_all_subclasses(RoutingPolicy) if c.type is not None} def get_policy(type: PolicyType) -> RoutingPolicy: return _POLICIES_[type]