Skip to content

Commit

Permalink
Merge pull request #15 from FreeTAKTeam/improved-registration
Browse files Browse the repository at this point in the history
Improved registration
  • Loading branch information
naman108 authored Nov 22, 2022
2 parents 7ff6a3a + 9816dc0 commit 68047e1
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 10 deletions.
17 changes: 15 additions & 2 deletions digitalpy/component/impl/default_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(
configuration=None,
configuration_path_template=None,
tracing_provider_instance=None,
manifest_path=None,
**kwargs,
):
super().__init__(
Expand All @@ -49,10 +50,17 @@ def __init__(
self.component_name = self.__class__.__name__

# get a tacer from the tracer provider

if tracing_provider_instance is not None:
self.tracer: Tracer = tracing_provider_instance.create_tracer(
component_name
)
else:
self.tracer = None
# load the manifest file as a configuration
if manifest_path is not None:
self.manifest = InifileConfiguration("")
self.manifest.add_configuration(manifest_path)

# define the logging
self.log_manager = LogManager()
Expand Down Expand Up @@ -101,15 +109,20 @@ def register(self, config: InifileConfiguration):
ObjectFactory.register_instance(
f"{self.component_name.lower()}actionmapper",
self.base.ActionMapper(
ObjectFactory.get_instance("event_manager"), internal_config
ObjectFactory.get_instance("event_manager"),
internal_config,
),
)
self._register_type_mapping()

def get_manifest(self):
"""returns the current manifest configuration"""
return self.manifest

def _register_type_mapping(self):
"""any component may or may not have a type mapping defined,
if it does then it should be registered"""
if self.type_mapping is not None:
if self.type_mapping:
request = ObjectFactory.get_new_instance("request")
request.set_action("RegisterMachineToHumanMapping")
request.set_value("machine_to_human_mapping", self.type_mapping)
Expand Down
20 changes: 16 additions & 4 deletions digitalpy/config/impl/inifile_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,19 @@ def get_key(self, value, section):
raise Exception(f"Value {value} not found in section {section}")
return map[value]

def get_section(self, section, include_meta=False):
def get_section(self, section: str, include_meta=False) -> dict:
"""get a section from the configuration as a dictionary
Args:
section (str): the name of the section to be retrieved
include_meta (bool, optional): whether or not to include values not prefixed by '__'. Defaults to False.
Raises:
ValueError: raised if the section is not found
Returns:
dict: a dictionary representing the contents of the section
"""
lookup_entry = self._lookup(section)
if lookup_entry is None:
raise ValueError(f"section {section} not found")
Expand Down Expand Up @@ -187,7 +199,7 @@ def build_lookup_table(self) -> Any:
lookup_section_key = section.lower() + ":"
self.lookup_table[lookup_section_key] = [section]
for key, _ in entry.items():
lookup_key = lookup_section_key.lower()+key
lookup_key = lookup_section_key.lower() + key
self.lookup_table[lookup_key] = {section: key}

def check_file_date(self, file_list: Any, reference_file: Any) -> Any:
Expand Down Expand Up @@ -245,7 +257,7 @@ def lookup(self, section: Any, key: Any = "") -> Any:
@return Array with section as first entry and key as second or None if not
found
"""
lookup_key = section.lower()+":"+key.lower()
lookup_key = section.lower() + ":" + key.lower()
if lookup_key in self.lookup_table:
return self.lookup_table[lookup_key]
else:
Expand Down Expand Up @@ -315,7 +327,7 @@ def set_value(
else:
final_key_name = key
self.config_array[final_section_name][final_key_name] = value
self.build_lookup_table()
self._build_lookup_table()

def unserialize(self, parsed_files: Any) -> Any:
"""Retrieve parsed ini data from the file system and update the current instance.
Expand Down
1 change: 1 addition & 0 deletions digitalpy/core/object_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def add_interfaces(self, interfaces):

@staticmethod
def clear():
"""clear the configured factory instance"""
if ObjectFactory.__factory != None:
ObjectFactory.__factory.clear()
ObjectFactory.__factory = None
Expand Down
Empty file.
158 changes: 158 additions & 0 deletions digitalpy/registration/registration_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import importlib
import os
from pathlib import PurePath
from typing import List
import pkg_resources

from digitalpy.component.impl.default_facade import DefaultFacade
from digitalpy.config.configuration import Configuration
from digitalpy.config.impl.inifile_configuration import InifileConfiguration

MANIFEST = "manifest"
DIGITALPY = "digitalpy"
REQUIRED_ALFA_VERSION = "requiredAlfaVersion"
NAME = "name"
VERSION = "version"
ID = "UUID"
VERSION_DELIMITER = "."


class RegistrationHandler:
"""this class is used to manage component registration"""

registered_components = {}

@staticmethod
def discover_components(component_folder_path: PurePath) -> List[str]:
"""this method is used to discover all available components
Args:
component_folder_path (str): the path in which to search for components. the searchable folder should be in the following format:\n
component_folder_path \n
|-- some_component \n
| `-- some_component_facade.py\n
`-- another_component\n
`-- another_component_facade.py\n
Returns:
List[str]: a list of available components in the given path
"""
potential_components = os.scandir(component_folder_path)
components = []
for potential_component in potential_components:
facade_path = PurePath(
potential_component.path, potential_component.name + "_facade.py"
)
if os.path.exists(facade_path):
components.append(PurePath(potential_component.path))
return components

@staticmethod
def register_component(
component_path: PurePath, import_root: str, config: InifileConfiguration
) -> bool:
"""this method is used to register a given component
Args:
component_path (PurePath): the path to the directory of the component to be registered.
import_root (str): the import root from which to import the components facade.
config (InifileConfiguration): the main configuration through which the components actions should be exposed.
Returns:
bool: whether or not the component was registered successfully
"""
try:
facade_path = PurePath(component_path, component_path.name + "_facade.py")
if os.path.exists(str(facade_path)):
component_name = component_path.name.replace("_component", "")

component_facade = getattr(
importlib.import_module(
f"{import_root}.{component_path.name}.{component_name}_facade"
),
f"{''.join([name.capitalize() if name[0].isupper()==False else name for name in component_name.split('_')])}",
)
facade_instance: DefaultFacade = component_facade(
None, None, None, None
)

if RegistrationHandler.validate_manifest(
facade_instance.get_manifest(), component_name
):
facade_instance.register(config)
else:
return False
else:
return False
RegistrationHandler.save_component(facade_instance.get_manifest(), component_name)
return True
except Exception as e:
# must use a print because logger may not be available
print(f"failed to register component: {component_path}, with error: {e}")
return False

@staticmethod
def save_component(manifest: Configuration, component_name: str):
section = manifest.get_section(component_name + MANIFEST, include_meta=True)
RegistrationHandler.registered_components[section[NAME]] = section

@staticmethod
def validate_manifest(manifest: Configuration, component_name: str) -> bool:
#TODO: determine better way to inform the caller that the manifest is invalid
"""validate that the component is compatible with the current digitalpy version
Args:
manifest (Configuration): the manifest of a component to be validated for this digitalpy installation
component_name (str): the name of the component to be validated
Raises:
ValueError: raised if the manifest section is missing from the manifest configuration
Returns:
bool: whether the component is compatible with the current digitalpy installation
"""
# retrieve the current digitalpy version based on the setup.py
digitalpy_version = pkg_resources.require(DIGITALPY)[0].version

try:
# get the manifest section from the configuration
section = manifest.get_section(component_name + MANIFEST, include_meta=True)
except ValueError:

raise ValueError(
f"manifest section missing, requires name {component_name+MANIFEST} please add the \
following section to the manifest [{component_name+MANIFEST}], for more information on component\
manifests please refer to the digitalpy documentation"
)

# validate the component name matches the name specified in the manifest
if component_name != section[NAME]:
return False

# iterate the delimited version number and compare it to the digitalpy version
for i in range(len(section[REQUIRED_ALFA_VERSION].split(VERSION_DELIMITER))):
#check if the version matches
digitalpy_version_number = digitalpy_version.split(VERSION_DELIMITER)[i] if len(digitalpy_version.split(VERSION_DELIMITER))>i else 0
if int(digitalpy_version_number)>=int(section[REQUIRED_ALFA_VERSION].split(VERSION_DELIMITER)[i]):
continue
else:
return False
if section[REQUIRED_ALFA_VERSION] != digitalpy_version:
return False

# dont approve the manifest if the component has already been registered
if (
component_name in RegistrationHandler.registered_components
and section[VERSION]
!= RegistrationHandler.registered_components[component_name][VERSION]
):
return False

# dont approve the manifest if a component with the same name but a different ID already exists
if (
component_name in RegistrationHandler.registered_components
and RegistrationHandler.registered_components[component_name][ID]
!= section[ID]
):
return False

return True
21 changes: 20 additions & 1 deletion digitalpy/routing/impl/default_action_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,34 @@ class DefaultActionMapper(ActionMapper):
# @param formatter
# @param configuration
#
def __init__(self, event_manager: EventManager, configuration: Configuration):
def __init__(
self,
event_manager: EventManager,
configuration: Configuration,
):
self.eventManager = event_manager
self.configuration = configuration
self.is_finished = False
self.tracing_provider = None

#
# @see ActionMapper.processAction()
#
def initialize_tracing(self):
try:
self.tracing_provider = ObjectFactory.get_instance("tracingprovider")
ObjectFactory.register_instance(
"tracingproviderinstance", self.tracing_provider
)
except Exception as e:
pass

def process_action(self, request: Request, response: Response):

# this is added for the sake of the latter use of multiprocessing
if not self.tracing_provider:
self.initialize_tracing()

self.eventManager.dispatch(
ApplicationEvent.NAME,
ApplicationEvent(ApplicationEvent.BEFORE_ROUTE_ACTION, request),
Expand Down
11 changes: 10 additions & 1 deletion digitalpy/routing/impl/default_routing_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def start(self):
self.initialize_tracing()
while True:
try:
print("listening")
message = self.sock.recv_multipart()
topic = message[0]

Expand Down Expand Up @@ -196,4 +197,12 @@ def start(self):
]
)
except Exception as e:
pass
try:
self.sock.send_multipart(
[
response_topic.encode("utf-8"),
f"error thrown {str(e)}".encode("utf-8"),
]
)
except Exception as e:
print(str(e))
7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from setuptools import setup, find_packages

setup(name='digitalpy',
version='0.2.4.3',
version='0.2.5.2',
description="A python implementation of the aphrodite's specification, heavily based on WCMF",
author='Natha Paquette',
author_email='[email protected]',
url='https://www.python.org/sigs/distutils-sig/',
install_requires=[
"rule-engine",
"pyzmq",
],
],
extras_require={
"DEV": ["pytest"],
},
packages=find_packages(include=["digitalpy", "digitalpy.*"])
)

0 comments on commit 68047e1

Please sign in to comment.