I am trying to create a python package which works with secret managers. Depending on which cloud the user is working (azure, gcp or aws) it should create the required secret manager of that particular cloud provider. For azure this is keyvaul and for gcp this is secret manager. To accomodate this I have created a function that can detect in which cloud the user is working. This will be sent to a Factory class that will create the corresponding secret manager, i.e., keyvault or secret manager etc. I have also created an Abstract class _SecretManager that will be used elsewhere in my code to provide a common interface to fetch secrets.
The problem lies within the type hinting the abstract method "_get_secret_manager". To make it work correctly when using mypy I have to add the following type hint: "Union[Keyvault, SecretManagerServiceClient]". But how can I extend this easily when we have say, 100 different cloud providers without having to write each possible return type for "_get_secret_manager"?
from __future__ import annotations
from abc import ABC, abstractmethod
from google.cloud.secretmanager import SecretManagerServiceClient
from azureml.core import Keyvault
class _SecretManager(ABC):
"""ABC Class for Cloud Secret Managers like Keyvault or GCP Secret Manager"""
@abstractmethod
def __init__(self) -> None:
self.manager = self._get_secret_manager()
@abstractmethod
def _get_secret_manager(self):
pass
@abstractmethod
def _get_secret(self, name: str):
pass
class _AzureKeyVault(_SecretManager):
"""_SecretManager Subclass for getting secrets from azure keyvault"""
def __init__(self) -> None:
self.manager = self._get_secret_manager()
def _get_secret_manager(self):
manager = Keyvault()
return manager
def _get_secret(self, name: str) -> str:
return self.manager.get_secret(name)
class _GCPSecretManager(_SecretManager):
"""_SecretManager Subclass for getting secrets from azure keyvault"""
def __init__(self) -> None:
self.manager = self._get_secret_manager()
def _get_secret_manager(self):
manager = SecretManagerServiceClient()
return manager
def _get_secret(self, name: str) -> str:
return self.manager.fetch_secret(name) #is actually a different method but for the sake of simpicity used this
class _SecretManagerFactory:
"""Factory for creating _SecretManager"""
def __init__(self) -> None:
self._managers = {"azure": _AzureKeyVault}
def create_secret_manager(self, cloud_provider, **kwargs) -> _SecretManager:
try:
manager = self._managers[cloud_provider]
except KeyError:
raise ValueError("This manager does not exist")
return manager(**kwargs)
I have tried the following:
class _SecretManager(ABC):
"""ABC Class for Cloud Secret Managers like Keyvault or GCP Secret Manager"""
@abstractmethod
def __init__(self) -> None:
self.manager = self._get_secret_manager()
@abstractmethod
def _get_secret_manager(self) -> _SecretManager:
pass
@abstractmethod
def _get_secret(self, name: str):
pass
but with this approach mypy will throw an error: "_SecretManager" has no attribute "get_secret"; maybe "_get_secret"? [attr-defined]. Which is kind of logical.