Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: implement abstract backend class #186

Merged
merged 1 commit into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 91 additions & 25 deletions eox_nelp/pearson_vue/rti_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
RealTimeImport: Class for managing RTI operations and executing the pipeline.
"""
import importlib
from abc import ABC, abstractmethod

from eox_nelp.pearson_vue.exceptions import PearsonBaseError
from eox_nelp.pearson_vue.pipeline import (
Expand All @@ -23,30 +24,39 @@
)


class RealTimeImport:
class AbstractBackend(ABC):
"""
Class for managing RTI (Real Time Import) operations and executing the pipeline.
Base class for managing backend operations and executing pipelines.

This class provides the core functionality to manage backend data and run a sequence of operations,
defined as a pipeline, to process the data. Subclasses must override the `get_pipeline` method
to provide specific pipeline functions.

Attributes:
backend_data (dict): A dictionary containing backend-specific data.

Methods:
run_pipeline(): Executes the RTI pipeline by iterating through the pipeline functions.
get_pipeline(): Returns the RTI pipeline, which is a list of functions to be executed.
run_pipeline(): Executes the pipeline by iterating through the pipeline functions.
get_pipeline(): Returns the pipeline, which is a list of functions to be executed (abstract method).
handle_error(exception: Exception, failed_step_pipeline: str): Handles errors during pipeline execution.
"""

def __init__(self, **kwargs):
"""
Initializes the RealTimeImport instance with the provided keyword arguments.
Initializes the AbstractBackend instance with the provided keyword arguments.

Args:
**kwargs: Additional keyword arguments to configure the RealTimeImport instance.
**kwargs: Additional keyword arguments to configure the AbstractBackend instance.
"""
self.backend_data = kwargs.copy()

def run_pipeline(self):
"""
Executes the RTI pipeline by iterating through the pipeline functions.
Executes the pipeline by iterating through the pipeline functions.

This method retrieves the pipeline using the `get_pipeline` method and executes each function
in the pipeline sequentially. If an error occurs during the execution of a function, it calls
`handle_error` method to handle the error and stops the pipeline execution.
"""
pipeline = self.get_pipeline()
pipeline_index = self.backend_data.get("pipeline_index", 0)
Expand All @@ -56,20 +66,85 @@ def run_pipeline(self):
try:
result = func(**self.backend_data) or {}
except PearsonBaseError as pearson_error:
tasks = importlib.import_module("eox_nelp.pearson_vue.tasks")
tasks.rti_error_handler_task.delay(
failed_step_pipeline=func.__name__,
exception_dict=pearson_error.to_dict(),
course_id=self.backend_data.get("course_id"),
user_id=self.backend_data.get("user_id"),
)
self.handle_error(pearson_error, func.__name__)
break

self.backend_data.update(result)
if result.get("safely_pipeline_termination"):
self.backend_data["pipeline_index"] = len(pipeline) - 1
break

@abstractmethod
def get_pipeline(self):
"""
Returns the pipeline, which is a list of functions to be executed.

Subclasses must override this method to provide the specific functions to be executed in the pipeline.

Returns:
list: A list of functions representing the pipeline.
"""

@abstractmethod
def handle_error(self, exception, failed_step_pipeline):
"""
Handles errors during pipeline execution.

Args:
exception (PearsonBaseError): The exception that was raised.
failed_step_pipeline (str): The name of the pipeline step where the error occurred.
"""


class ErrorRealTimeImportHandler(AbstractBackend):
"""Class for managing validation error pipe executing the pipeline for data validation."""

def handle_error(self, exception, failed_step_pipeline):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be removed.
In this case is doing nothing... or maybe we're gonna manage the error in the error pipeline?

This PR is changing because in the past the error pipeline could fail and call again the error pipeline...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nop, this is necessary since the parent class is an abstract class , so all methods defined as abstract methods have to be implemented in the subclasses

"""
Handles errors during pipeline execution.

Args:
exception (Exception): The exception that was raised.
failed_step_pipeline (str): The name of the pipeline step where the error occurred.
"""

def get_pipeline(self):
"""
Returns the error validation pipeline, which is a list of functions to be executed.
"""
return [
audit_pearson_error,
]


class RealTimeImport(AbstractBackend):
"""
Class for managing RTI (Real Time Import) operations and executing the pipeline.

Attributes:
backend_data (dict): A dictionary containing backend-specific data.

Methods:
run_pipeline(): Executes the RTI pipeline by iterating through the pipeline functions.
get_pipeline(): Returns the RTI pipeline, which is a list of functions to be executed.
"""

def handle_error(self, exception, failed_step_pipeline):
"""
Handles errors during pipeline execution.

Args:
exception (Exception): The exception that was raised.
failed_step_pipeline (str): The name of the pipeline step where the error occurred.
"""
tasks = importlib.import_module("eox_nelp.pearson_vue.tasks")
tasks.rti_error_handler_task.delay(
failed_step_pipeline=failed_step_pipeline,
exception_dict=exception.to_dict(),
course_id=self.backend_data.get("course_id"),
user_id=self.backend_data.get("user_id"),
)

def get_pipeline(self):
"""
Returns the RTI pipeline, which is a list of functions to be executed.
Expand All @@ -90,6 +165,7 @@ def get_pipeline(self):

class ExamAuthorizationDataImport(RealTimeImport):
"""Class for EAD requests (Exam Authorization Data operations) and executing the pipeline."""

def get_pipeline(self):
"""
Returns the EAD pipeline, which is a list of functions to be executed.
Expand All @@ -106,6 +182,7 @@ def get_pipeline(self):

class CandidateDemographicsDataImport(RealTimeImport):
"""Class for CDD requests (Candidate Demographics Data operations) and executing the pipeline."""

def get_pipeline(self):
"""
Returns the CDD pipeline, which is a list of functions to be executed.
Expand All @@ -117,14 +194,3 @@ def get_pipeline(self):
check_service_availability,
import_candidate_demographics,
]


class ErrorRealTimeImportHandler(RealTimeImport):
"""Class for managing validation error pipe executing the pipeline for data validation."""
def get_pipeline(self):
"""
Returns the error validation pipeline, which is a list of functions to be executed.
"""
return [
audit_pearson_error,
]
44 changes: 25 additions & 19 deletions eox_nelp/pearson_vue/tests/test_rti_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
)


@ddt
class TestRealTimeImport(unittest.TestCase):
class TestAbstractBackendMixin:
"""
Unit tests for the RealTimeImport class.
Unit tests for the AbstracBackend subclasses.
"""
rti_backend_class = RealTimeImport

def setUp(self):
def setUp(self): # pylint: disable=invalid-name
"""
Set up the test environment.
"""
Expand Down Expand Up @@ -145,6 +143,27 @@ def test_safely_pipeline_termination(self):
},
)

def test_get_pipeline(self):
"""
Test the retrieval of the RTI pipeline.

Expected behavior:
- Method return a list instance
- All the pipeline items are callable.
"""
pipeline = self.rti.get_pipeline()

self.assertIsInstance(pipeline, list)
self.assertTrue(all(callable(func) for func in pipeline))


@ddt
class TestRealTimeImport(TestAbstractBackendMixin, unittest.TestCase):
"""
Unit tests for the RealTimeImport class.
"""
rti_backend_class = RealTimeImport

@data(
PearsonValidationError(inspect.currentframe(), "error: ['String to short.']"),
PearsonKeyError(inspect.currentframe(), "eligibility_appt_date_first"),
Expand Down Expand Up @@ -198,19 +217,6 @@ def test_launch_validation_error_pipeline(self, pearson_error, rti_error_handler
course_id=None,
)

def test_get_pipeline(self):
"""
Test the retrieval of the RTI pipeline.

Expected behavior:
- Method return a list instance
- All the pipeline items are callable.
"""
pipeline = self.rti.get_pipeline()

self.assertIsInstance(pipeline, list)
self.assertTrue(all(callable(func) for func in pipeline))


class TestExamAuthorizationDataImport(TestRealTimeImport):
"""
Expand All @@ -226,7 +232,7 @@ class TestCandidateDemographicsDataImport(TestRealTimeImport):
rti_backend_class = CandidateDemographicsDataImport


class TestErrorRealTimeImportHandler(TestRealTimeImport):
class TestErrorRealTimeImportHandler(TestAbstractBackendMixin, unittest.TestCase):
"""
Unit tests for the rti_backend class.
"""
Expand Down
Loading