Diferencia entre revisiones de «Desarrollo de frameworks»
(→Preparación para práctica sobre creación de frameworks) |
|||
(No se muestran 10 ediciones intermedias del mismo usuario) | |||
Línea 11: | Línea 11: | ||
En esta práctica recrearemos un desarrollo de un framework para el analisis de modelos de variabilidad. | En esta práctica recrearemos un desarrollo de un framework para el analisis de modelos de variabilidad. | ||
+ | |||
+ | Podréis encontrar el código en github [https://github.com/EGCETSII/framework aquí] | ||
+ | |||
+ | Presentación en pdf [https://hdvirtual.us.es/discovirt/index.php/s/N54RrWogNwQkPAb aquí] | ||
+ | |||
+ | Clase grabada [https://hdvirtual.us.es/discovirt/index.php/s/k7G3cpBdyNc4cY5 aquí] | ||
=Ejercicio= | =Ejercicio= | ||
Línea 17: | Línea 23: | ||
----------------------- | ----------------------- | ||
− | * Análisis | + | * Análisis: Identificamos los frozen points de nuestro framework |
− | Identificamos los frozen points de nuestro framework | + | ** Clases del dominio: El dominio sobre el que desarrollaremos nuestro framework es el del analisis de modelos de características. Para ello, comenzaremos por conocer los elementos básicos de los mismos según lo visto en teoría. |
+ | ** Gestión de plugins: También analizaremos que partes del framework permitirán extensivilidad basandonos en plugins. | ||
+ | |||
+ | * Diseño: En este apartado crearemos una clase Feature y una clase Relación para definir los conceptos con los que trabajará el framework | ||
+ | fichero: core/frozen_points_domain.py | ||
+ | <syntaxhighlight lang="python" line='line'> | ||
+ | from typing import Sequence | ||
+ | #Este modulo añade soporte a clases abstractas | ||
+ | from abc import ABC, abstractmethod | ||
+ | |||
+ | #Esto es la definición de la clase Relación | ||
+ | class Relation: | ||
+ | |||
+ | #Esto es un constructor en python | ||
+ | #Cuando colocamos el nombre de la clase entre comillas es la forma de usar tipado fuerte en python. | ||
+ | #De cara a crear un framework de caja blanca, necesitamos orientación a objetos y tipado de objectos | ||
+ | #Esto está disponible en python desde la versión 3.8 en adelante | ||
+ | def __init__(self, parent: 'Feature', children: Sequence['Feature'], card_min: int, card_max: int): | ||
+ | self.parent = parent | ||
+ | self.children = children | ||
+ | self.card_min = card_min | ||
+ | self.card_max = card_max | ||
+ | |||
+ | #Este método añade un elemento a la colección de hijos de una relación | ||
+ | def add_child(self, feature: 'Feature'): | ||
+ | self.children.append(feature) | ||
+ | |||
+ | #Este método verifica la cardinalidad de la relación y el número de hijos para devolver true si es mandatory | ||
+ | def is_mandatory(self) -> bool: | ||
+ | return (len(self.children) == 1 and self.card_max == 1 and self.card_min == 1) | ||
+ | |||
+ | #Este método verifica la cardinalidad de la relación y el número de hijos para devolver true si es optional | ||
+ | def is_optional(self) -> bool: | ||
+ | return (len(self.children) == 1 and self.card_max == 1 and self.card_min == 0) | ||
+ | |||
+ | #Este método verifica la cardinalidad de la relación y el número de hijos para devolver true si es or | ||
+ | def is_or(self) -> bool: | ||
+ | return (len(self.children) > 1 and self.card_max == len(self.children) and self.card_min == 1) | ||
+ | |||
+ | #Este método verifica la cardinalidad de la relación y el número de hijos para devolver true si es alternative | ||
+ | def is_alternative(self) -> bool: | ||
+ | return (len(self.children) > 1 and self.card_max == 1 and self.card_min == 1) | ||
+ | |||
+ | #Este método to string en python | ||
+ | def __str__(self): | ||
+ | res = self.parent.name + '[' + str(self.card_min) + ',' + str(self.card_max) + ']' | ||
+ | for _child in self.children: | ||
+ | res += _child.name + ' ' | ||
+ | return res | ||
+ | |||
+ | #Esto es la definición de la clase Feature | ||
+ | class Feature: | ||
+ | |||
+ | #Este es el método constructor. Aqui definimos que queremos que cada característica tenga un nombre y un conjunto de relaciones. | ||
+ | def __init__(self, name: str, relations: Sequence['Relation']): | ||
+ | self.name = name | ||
+ | self.relations = relations | ||
+ | |||
+ | #Este es un metodo auxiliar para añadir una relación a una característica | ||
+ | def add_relation(self, relation: 'Relation'): | ||
+ | self.relations.append(relation) | ||
+ | |||
+ | #Este método nos devuelve el conjunto de relaciones de una característica | ||
+ | def get_relations(self): | ||
+ | return self.relations | ||
+ | |||
+ | #Este método to string en python | ||
+ | def __str__(self): | ||
+ | return self.name | ||
+ | |||
+ | #Esta clase representa a un modelo de características | ||
+ | class FeatureModel(): | ||
+ | |||
+ | #En el constructor solo encontramos una feature raiz | ||
+ | def __init__(self, root: Feature): | ||
+ | self.root = root | ||
+ | |||
+ | #Este método devuelve el conjunto de relaciones existentes en el modelo | ||
+ | def get_relations(self, feature=None): | ||
+ | relations = [] | ||
+ | if not feature: | ||
+ | feature = self.root | ||
+ | for relation in feature.relations: | ||
+ | relations.append(relation) | ||
+ | for _feature in relation.children: | ||
+ | relations.extend(self.get_relations(_feature)) | ||
+ | return relations | ||
+ | |||
+ | #Este método devuelve el conjunto de features de un modelo | ||
+ | def get_features(self): | ||
+ | features = [] | ||
+ | features.append(self.root) | ||
+ | for relation in self.get_relations(): | ||
+ | features.extend(relation.children) | ||
+ | return features | ||
+ | |||
+ | #Este método devuelve la feature identificandola por el nombre (TODO: Implementar con __equals__) | ||
+ | def get_feature_by_name(self, str) -> Feature: | ||
+ | features = self.get_features | ||
+ | for feat in features: | ||
+ | if feat.name == str: | ||
+ | return feat | ||
+ | raise ElementNotFoundException | ||
− | + | #Este método to string en python | |
− | + | def __str__(self) -> str: | |
− | + | res = 'root: ' + self.root.name + '\r\n' | |
+ | for i, relation in enumerate(self.get_relations()): | ||
+ | res += f'relation {i}: {relation}\r\n' | ||
+ | return(res) | ||
− | + | #This is an abstract Operation. To define abstract methos we rely on ABC module of the core python distribution | |
− | + | class Operation(ABC): | |
− | |||
− | + | #This abstract method, executes an operation given a feature model | |
− | + | @abstractmethod | |
+ | def execute(self, model: FeatureModel) -> 'Operation': | ||
+ | pass | ||
+ | </syntaxhighlight> | ||
− | < | + | También implementamos el sistema dentro del core para la carga de plugins |
− | + | fichero: core/frozen_points_plugins.py | |
− | + | <syntaxhighlight lang="python" line='line'> | |
+ | from types import FunctionType, ModuleType | ||
+ | from typing import List | ||
− | + | # Este modulo nos permite obligar ciertas listas con tipos | |
+ | from collections import UserList | ||
+ | # Estos modulos nos permiten explorar las clases existentes en distintos plugins 'a la java reflexion' | ||
+ | from importlib import import_module | ||
+ | from pkgutil import iter_modules | ||
+ | import inspect | ||
− | + | # Importamos las clases del core que vamos a necesitar (blackbox) | |
− | + | from core.frozen_points_domain import Operation | |
− | + | from core.frozen_points_domain import FeatureModel | |
− | + | # Definimos las posibles rutas a los plugins | |
− | + | PLUGIN_PATHS = [ | |
− | + | 'plugin', | |
− | + | ] | |
− | |||
− | + | # Definimos una coleccion para almacenar los objetos de tipo operacion. | |
− | ------------- | + | # Es interesante que las colecciones tipadas funcionan como clases y pueden tener métodos |
+ | class Operations(UserList): # pylint: disable=too-many-ancestors | ||
+ | data: List[Operation] | ||
+ | |||
+ | def search_by_name(self, name: str) -> Operation: | ||
+ | candidates = filter(lambda op: op.__name__ == name, self.data) | ||
+ | try: | ||
+ | operation = next(candidates, None) | ||
+ | except StopIteration: | ||
+ | raise OperationNotFound | ||
+ | return operation | ||
+ | |||
+ | # Definimos una clase que represente a un plugin dentro del framwork. | ||
+ | # Notese que también contiene los modulos y las operaciones que haya en la instalación de python | ||
+ | class Plugin: | ||
+ | def __init__(self, module: ModuleType) -> None: | ||
+ | self.module: ModuleType = module | ||
+ | self.operations: Operations = Operations() | ||
+ | |||
+ | @property | ||
+ | def name(self): | ||
+ | return self.module.__name__.split('.')[-1] | ||
+ | |||
+ | # Esta clase añade las operaciones de un modulo | ||
+ | def append_operation(self, operation: Operation) -> None: | ||
+ | self.operations.append(operation) | ||
+ | |||
+ | def use_operation(self, name: str, src: FeatureModel) -> Operation: | ||
+ | operation = self.operations.search_by_name(name) | ||
+ | return operation().execute(model=src) | ||
+ | |||
+ | # Definimos una lista de plugins y algunos métodos importantes | ||
+ | class Plugins(UserList): # pylint: disable=too-many-ancestors | ||
+ | data: List[Plugin] | ||
+ | |||
+ | # buscamos los plugins que se llamen como queremos | ||
+ | def get_plugin_by_name(self, name: str): | ||
+ | for plugin in self.data: | ||
+ | if plugin.name == name: | ||
+ | return plugin | ||
+ | raise PluginNotFound | ||
+ | |||
+ | # devolvemos la lista de plugins | ||
+ | def get_plugin_names(self) -> List[str]: | ||
+ | return [plugin.name for plugin in self.data] | ||
+ | |||
+ | #devolvemos las operacione sde un plugin | ||
+ | def get_operations_by_plugin_name(self, plugin_name: str) -> Operations: | ||
+ | try: | ||
+ | plugin = self.get_plugin_by_name(plugin_name) | ||
+ | return plugin.operations | ||
+ | except PluginNotFound: | ||
+ | return Operations() | ||
+ | |||
+ | #Esta es una clase especial que nos va a permitir enumerar y ejecutar las operaciones que tengamos dentro de los plugins | ||
+ | class DiscoverMetamodels: | ||
+ | |||
+ | #Cuando creamos la clase, esta inicializa la lista de modulos existentes y llama al discover para buscar plugins y modulos | ||
+ | def __init__(self): | ||
+ | self.module_paths: List[ModuleType] = list() | ||
+ | for path in PLUGIN_PATHS: | ||
+ | try: | ||
+ | module: ModuleType = import_module(path) | ||
+ | self.module_paths.append(module) | ||
+ | except ModuleNotFoundError: | ||
+ | print('ModuleNotFoundError %s', path) | ||
+ | self.plugins: Plugins = self.discover() | ||
+ | |||
+ | #Este metodo busca las clases existentes en los módulos encontrados | ||
+ | def search_classes(self, module): | ||
+ | classes = [] | ||
+ | for _, file_name, ispkg in iter_modules( module.__path__, module.__name__ + '.' ): | ||
+ | if ispkg: | ||
+ | classes += self.search_classes(import_module(file_name)) | ||
+ | else: | ||
+ | _file = import_module(file_name) | ||
+ | classes += inspect.getmembers(_file, inspect.isclass) | ||
+ | return classes | ||
+ | |||
+ | # Este método se encarga de buscar los plugins modulos y clases existentes en el path indicado como variable global | ||
+ | def discover(self) -> dict: | ||
+ | plugins = Plugins() | ||
+ | for pkg in self.module_paths: | ||
+ | for _, plugin_name, ispkg in iter_modules(pkg.__path__, pkg.__name__ + '.'): | ||
+ | if not ispkg: | ||
+ | continue | ||
+ | module = import_module(plugin_name) | ||
+ | plugin = Plugin(module=module) | ||
+ | classes = self.search_classes(module) | ||
+ | for _, _class in classes: | ||
+ | if not _class.__module__.startswith(module.__package__): | ||
+ | continue # Exclude modules not in current package | ||
+ | #!! Fijaos como añadimos a la colección de operaciones cuando la clase operaciones hereda de la clase abstracta !! | ||
+ | inherit = _class.mro() | ||
+ | if Operation in inherit: | ||
+ | plugin.append_operation(_class) | ||
+ | plugins.append(plugin) | ||
+ | return plugins | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Finalmente implementaremos un plugin | ||
+ | fichero: plugin/count_leafs/count_leafs_op.py | ||
+ | <syntaxhighlight lang="python" line='line'> | ||
+ | from core.frozen_points_domain import Operation | ||
+ | from core.frozen_points_domain import FeatureModel | ||
+ | |||
+ | class CountLeafs(Operation): | ||
+ | |||
+ | def execute(self, model: FeatureModel) -> 'Operation': | ||
+ | result = 0 | ||
+ | for feat in model.get_features(): | ||
+ | if len(feat.get_relations())==0: | ||
+ | result= result +1 | ||
+ | return result | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | * Instanciación: Finalmente implementaremos una aplicación que consuma tanto nuestros módulos del core como el plugin creado. | ||
+ | fichero:./main.py | ||
+ | <syntaxhighlight lang="python" line='line'> | ||
+ | |||
+ | from core.frozen_points_domain import * | ||
+ | from core.frozen_points_plugins import DiscoverMetamodels | ||
+ | |||
+ | # Creamos el manager y lo inicializamos | ||
+ | dm = DiscoverMetamodels() | ||
+ | #Buscamos los plugins disponibles | ||
+ | available_plugins = dm.discover() | ||
+ | |||
+ | #Creamos un modelo de manera programatica | ||
+ | feature_b = Feature('B', []) | ||
+ | relation = Relation(parent=None, children=[feature_b], card_min=0, card_max=1) | ||
+ | feature_a = Feature('A', [relation]) | ||
+ | relation.parent = feature_a | ||
+ | fm = FeatureModel(feature_a) | ||
+ | |||
+ | #Imprimimos ese modelo | ||
+ | print(fm) | ||
+ | |||
+ | #Imprimimos los plugins disponibles | ||
+ | print(available_plugins.get_plugin_names()) | ||
− | + | #Buscamos el plugin que acabamos de crear | |
− | + | plugin=available_plugins.get_plugin_by_name('count_leafs') | |
− | + | #Imprimimos las operaciones de los plugins | |
− | + | print(plugin.operations) | |
+ | #Usamos la operacion CountLeafs | ||
+ | result=plugin.use_operation('CountLeafs',fm) | ||
− | + | #Imprimimos el resultado | |
− | + | print("El modelo tiene " + result + "Features hojas") | |
− | + | </syntaxhighlight> | |
− | |||
− | </ | ||
− | + | --IMPORTANTE-- | |
+ | Añadir los ficheros __init__.py para que las carpetas core y core_leafs sean modulos Python | ||
− | + | Iteración 2 | |
− | + | ----------------------- | |
− | + | Se ha introducido un nuevo requisito para un nuevo producto en el que se nos pide una operación que cuente el número de productos del modelo. TIEMPO 20 minutos | |
− | **Instanciación | + | * Análisis: Identificamos los frozen points de nuestro framework al solicitarnos la implementación de una operación que nos diga cuantas características tiene un modelo |
− | Finalmente implementaremos una aplicación que consuma tanto nuestros módulos del core como el nuevo plugin creado. | + | ** Clases del dominio: Añadiremos las clases necesarias para implementar distintos tipos de serializadores de ficheros. |
− | + | * Diseño: En este apartado crearemos un nuevo plugin que implemente esa operación | |
− | + | * Instanciación: Finalmente implementaremos una aplicación que consuma tanto nuestros módulos del core como el nuevo plugin creado. | |
− |
Revisión actual del 14:00 15 ene 2021
Prerrequisitos
Para esta práctica solo será necesario que nos aseguremos de tener instalada una versión igual o mayor a Python 3.8.
Para verificar la versión de Python que tenemos instalada podemos ejecutar:
python -V
Preparación para práctica sobre creación de frameworks
En esta práctica recrearemos un desarrollo de un framework para el analisis de modelos de variabilidad.
Podréis encontrar el código en github aquí
Presentación en pdf aquí
Clase grabada aquí
Ejercicio
Iteración 1
- Análisis: Identificamos los frozen points de nuestro framework
- Clases del dominio: El dominio sobre el que desarrollaremos nuestro framework es el del analisis de modelos de características. Para ello, comenzaremos por conocer los elementos básicos de los mismos según lo visto en teoría.
- Gestión de plugins: También analizaremos que partes del framework permitirán extensivilidad basandonos en plugins.
- Diseño: En este apartado crearemos una clase Feature y una clase Relación para definir los conceptos con los que trabajará el framework
fichero: core/frozen_points_domain.py
from typing import Sequence
#Este modulo añade soporte a clases abstractas
from abc import ABC, abstractmethod
#Esto es la definición de la clase Relación
class Relation:
#Esto es un constructor en python
#Cuando colocamos el nombre de la clase entre comillas es la forma de usar tipado fuerte en python.
#De cara a crear un framework de caja blanca, necesitamos orientación a objetos y tipado de objectos
#Esto está disponible en python desde la versión 3.8 en adelante
def __init__(self, parent: 'Feature', children: Sequence['Feature'], card_min: int, card_max: int):
self.parent = parent
self.children = children
self.card_min = card_min
self.card_max = card_max
#Este método añade un elemento a la colección de hijos de una relación
def add_child(self, feature: 'Feature'):
self.children.append(feature)
#Este método verifica la cardinalidad de la relación y el número de hijos para devolver true si es mandatory
def is_mandatory(self) -> bool:
return (len(self.children) == 1 and self.card_max == 1 and self.card_min == 1)
#Este método verifica la cardinalidad de la relación y el número de hijos para devolver true si es optional
def is_optional(self) -> bool:
return (len(self.children) == 1 and self.card_max == 1 and self.card_min == 0)
#Este método verifica la cardinalidad de la relación y el número de hijos para devolver true si es or
def is_or(self) -> bool:
return (len(self.children) > 1 and self.card_max == len(self.children) and self.card_min == 1)
#Este método verifica la cardinalidad de la relación y el número de hijos para devolver true si es alternative
def is_alternative(self) -> bool:
return (len(self.children) > 1 and self.card_max == 1 and self.card_min == 1)
#Este método to string en python
def __str__(self):
res = self.parent.name + '[' + str(self.card_min) + ',' + str(self.card_max) + ']'
for _child in self.children:
res += _child.name + ' '
return res
#Esto es la definición de la clase Feature
class Feature:
#Este es el método constructor. Aqui definimos que queremos que cada característica tenga un nombre y un conjunto de relaciones.
def __init__(self, name: str, relations: Sequence['Relation']):
self.name = name
self.relations = relations
#Este es un metodo auxiliar para añadir una relación a una característica
def add_relation(self, relation: 'Relation'):
self.relations.append(relation)
#Este método nos devuelve el conjunto de relaciones de una característica
def get_relations(self):
return self.relations
#Este método to string en python
def __str__(self):
return self.name
#Esta clase representa a un modelo de características
class FeatureModel():
#En el constructor solo encontramos una feature raiz
def __init__(self, root: Feature):
self.root = root
#Este método devuelve el conjunto de relaciones existentes en el modelo
def get_relations(self, feature=None):
relations = []
if not feature:
feature = self.root
for relation in feature.relations:
relations.append(relation)
for _feature in relation.children:
relations.extend(self.get_relations(_feature))
return relations
#Este método devuelve el conjunto de features de un modelo
def get_features(self):
features = []
features.append(self.root)
for relation in self.get_relations():
features.extend(relation.children)
return features
#Este método devuelve la feature identificandola por el nombre (TODO: Implementar con __equals__)
def get_feature_by_name(self, str) -> Feature:
features = self.get_features
for feat in features:
if feat.name == str:
return feat
raise ElementNotFoundException
#Este método to string en python
def __str__(self) -> str:
res = 'root: ' + self.root.name + '\r\n'
for i, relation in enumerate(self.get_relations()):
res += f'relation {i}: {relation}\r\n'
return(res)
#This is an abstract Operation. To define abstract methos we rely on ABC module of the core python distribution
class Operation(ABC):
#This abstract method, executes an operation given a feature model
@abstractmethod
def execute(self, model: FeatureModel) -> 'Operation':
pass
También implementamos el sistema dentro del core para la carga de plugins fichero: core/frozen_points_plugins.py
from types import FunctionType, ModuleType
from typing import List
# Este modulo nos permite obligar ciertas listas con tipos
from collections import UserList
# Estos modulos nos permiten explorar las clases existentes en distintos plugins 'a la java reflexion'
from importlib import import_module
from pkgutil import iter_modules
import inspect
# Importamos las clases del core que vamos a necesitar (blackbox)
from core.frozen_points_domain import Operation
from core.frozen_points_domain import FeatureModel
# Definimos las posibles rutas a los plugins
PLUGIN_PATHS = [
'plugin',
]
# Definimos una coleccion para almacenar los objetos de tipo operacion.
# Es interesante que las colecciones tipadas funcionan como clases y pueden tener métodos
class Operations(UserList): # pylint: disable=too-many-ancestors
data: List[Operation]
def search_by_name(self, name: str) -> Operation:
candidates = filter(lambda op: op.__name__ == name, self.data)
try:
operation = next(candidates, None)
except StopIteration:
raise OperationNotFound
return operation
# Definimos una clase que represente a un plugin dentro del framwork.
# Notese que también contiene los modulos y las operaciones que haya en la instalación de python
class Plugin:
def __init__(self, module: ModuleType) -> None:
self.module: ModuleType = module
self.operations: Operations = Operations()
@property
def name(self):
return self.module.__name__.split('.')[-1]
# Esta clase añade las operaciones de un modulo
def append_operation(self, operation: Operation) -> None:
self.operations.append(operation)
def use_operation(self, name: str, src: FeatureModel) -> Operation:
operation = self.operations.search_by_name(name)
return operation().execute(model=src)
# Definimos una lista de plugins y algunos métodos importantes
class Plugins(UserList): # pylint: disable=too-many-ancestors
data: List[Plugin]
# buscamos los plugins que se llamen como queremos
def get_plugin_by_name(self, name: str):
for plugin in self.data:
if plugin.name == name:
return plugin
raise PluginNotFound
# devolvemos la lista de plugins
def get_plugin_names(self) -> List[str]:
return [plugin.name for plugin in self.data]
#devolvemos las operacione sde un plugin
def get_operations_by_plugin_name(self, plugin_name: str) -> Operations:
try:
plugin = self.get_plugin_by_name(plugin_name)
return plugin.operations
except PluginNotFound:
return Operations()
#Esta es una clase especial que nos va a permitir enumerar y ejecutar las operaciones que tengamos dentro de los plugins
class DiscoverMetamodels:
#Cuando creamos la clase, esta inicializa la lista de modulos existentes y llama al discover para buscar plugins y modulos
def __init__(self):
self.module_paths: List[ModuleType] = list()
for path in PLUGIN_PATHS:
try:
module: ModuleType = import_module(path)
self.module_paths.append(module)
except ModuleNotFoundError:
print('ModuleNotFoundError %s', path)
self.plugins: Plugins = self.discover()
#Este metodo busca las clases existentes en los módulos encontrados
def search_classes(self, module):
classes = []
for _, file_name, ispkg in iter_modules( module.__path__, module.__name__ + '.' ):
if ispkg:
classes += self.search_classes(import_module(file_name))
else:
_file = import_module(file_name)
classes += inspect.getmembers(_file, inspect.isclass)
return classes
# Este método se encarga de buscar los plugins modulos y clases existentes en el path indicado como variable global
def discover(self) -> dict:
plugins = Plugins()
for pkg in self.module_paths:
for _, plugin_name, ispkg in iter_modules(pkg.__path__, pkg.__name__ + '.'):
if not ispkg:
continue
module = import_module(plugin_name)
plugin = Plugin(module=module)
classes = self.search_classes(module)
for _, _class in classes:
if not _class.__module__.startswith(module.__package__):
continue # Exclude modules not in current package
#!! Fijaos como añadimos a la colección de operaciones cuando la clase operaciones hereda de la clase abstracta !!
inherit = _class.mro()
if Operation in inherit:
plugin.append_operation(_class)
plugins.append(plugin)
return plugins
Finalmente implementaremos un plugin fichero: plugin/count_leafs/count_leafs_op.py
from core.frozen_points_domain import Operation
from core.frozen_points_domain import FeatureModel
class CountLeafs(Operation):
def execute(self, model: FeatureModel) -> 'Operation':
result = 0
for feat in model.get_features():
if len(feat.get_relations())==0:
result= result +1
return result
- Instanciación: Finalmente implementaremos una aplicación que consuma tanto nuestros módulos del core como el plugin creado.
fichero:./main.py
from core.frozen_points_domain import *
from core.frozen_points_plugins import DiscoverMetamodels
# Creamos el manager y lo inicializamos
dm = DiscoverMetamodels()
#Buscamos los plugins disponibles
available_plugins = dm.discover()
#Creamos un modelo de manera programatica
feature_b = Feature('B', [])
relation = Relation(parent=None, children=[feature_b], card_min=0, card_max=1)
feature_a = Feature('A', [relation])
relation.parent = feature_a
fm = FeatureModel(feature_a)
#Imprimimos ese modelo
print(fm)
#Imprimimos los plugins disponibles
print(available_plugins.get_plugin_names())
#Buscamos el plugin que acabamos de crear
plugin=available_plugins.get_plugin_by_name('count_leafs')
#Imprimimos las operaciones de los plugins
print(plugin.operations)
#Usamos la operacion CountLeafs
result=plugin.use_operation('CountLeafs',fm)
#Imprimimos el resultado
print("El modelo tiene " + result + "Features hojas")
--IMPORTANTE-- Añadir los ficheros __init__.py para que las carpetas core y core_leafs sean modulos Python
Iteración 2
Se ha introducido un nuevo requisito para un nuevo producto en el que se nos pide una operación que cuente el número de productos del modelo. TIEMPO 20 minutos
- Análisis: Identificamos los frozen points de nuestro framework al solicitarnos la implementación de una operación que nos diga cuantas características tiene un modelo
- Clases del dominio: Añadiremos las clases necesarias para implementar distintos tipos de serializadores de ficheros.
- Diseño: En este apartado crearemos un nuevo plugin que implemente esa operación
- Instanciación: Finalmente implementaremos una aplicación que consuma tanto nuestros módulos del core como el nuevo plugin creado.