import re
import yaml
from dependency_injector import providers
[docs]
class ContainerIOC:
"""
Cette classe permet de faire le lien entre la ligne de commande et le code. Il s'agit d'un conteneur d'inversion de
dépendance. Celle-ci est fortement inspiré du conteneur builder de Symfony (framework php).
Cette classe permet l'instanciation de modules dynamiquement à partir de la configuration par défaut
de l'application stockée dans un fichier yaml
"""
def __init__(self, config_file: str):
"""
Constructeur de la classe ContainerIOC, cette méthode prend en paramètre un fichier yaml contenant la
configuration par défaut de l'application
Args:
config_file (str): un fichier de configuration au format yaml
"""
self.container: dict = load_from_yaml(config_file)
[docs]
def get(self, name: str, **kwargs):
"""
Cette fonction permet d'accéder à différentes variables stockées dans le conteneur. Elle se charge également
de l'instantiation des différents modules à partir de valeurs par défaut contenu dans un fichier yaml.
Ces valeurs par défaut peuvent être écrasé par l'utilisateur s'il les spécifie dans les paramètres kwargs.
Args:
name (str): nom du service/variable souhaité
**kwargs : arguments utilisés lors de l'instanciation d'un service
Returns: Une variable/Un service
"""
# Si le conteneur ne contient pas l'attribut on renvoie une erreur
attr = self.container.get(name)
if attr is None:
raise DependencyNotFoundError(name)
if isinstance(attr, providers.Factory):
if not set(kwargs.keys()) <= set(attr.kwargs['args'].keys()):
raise Exception(f"bad arguments was given to create {name} tool")
for key, value in attr.kwargs['args'].items():
if isinstance(value, str):
attr.kwargs['args'][key] = self._resolver(value)
attr.kwargs['args'].update(kwargs)
return attr()
else:
if isinstance(attr, str):
return self._resolver(attr)
else:
return attr
[docs]
def set(self, name, value):
"""
Cette méthode permet d'insérer un nouvel objet dans le conteneur.
Args:
name: nom de l'objet qui sera utilisé par la suite pour y accéder
value: objet à insérer
"""
self.container[name] = value
def _resolver(self, string: str) -> str:
"""
Méthode interne de la classe ContainerIOC. Elle permet de résoudre l'inclusion de variables dans des
string par un appel récursif.
Returns:
str : Une chaîne de caractère dont les variables inclues ont été inséré
"""
def replace_match(match):
# Retourne le remplacement correspondant
return self.container.get(match.group(1), match.group(0))
# Pattern pour trouver les segments encadrés par des %
pattern = r"%([^%]+)%"
# Remplace les segments
nouvelle_chaine = re.sub(pattern, replace_match, string)
return nouvelle_chaine
[docs]
class DependencyNotFoundError(Exception):
def __init__(self, dependency_name):
self.dependency_name = dependency_name
self.message = f"La dépendance '{self.dependency_name}' n'est pas enregistrée dans le conteneur."
super().__init__(self.message)
[docs]
def load_from_yaml(file_path) -> dict:
with open(file_path, 'r') as yaml_file:
services_data = yaml.safe_load(yaml_file)
yaml_content = services_data.get('values')
for service_name, service_data in services_data.get('tools', {}).items():
class_path = service_data.get('class')
args = service_data.get('arguments', {})
provider = providers.Factory(create_instance, class_path=class_path, args=args)
yaml_content[service_name] = provider
return yaml_content
[docs]
def create_instance(class_path, args):
parts = class_path.split('.')
module_path = '.'.join(parts[:-1])
class_name = parts[-1]
module = __import__(module_path, fromlist=[class_name])
clazz = getattr(module, class_name)
return clazz(**args)