Skip to content

Commit

Permalink
Delete generics and tidy up
Browse files Browse the repository at this point in the history
  • Loading branch information
BenTalese committed Jul 22, 2023
1 parent 43d5916 commit 70a95ea
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 67 deletions.
9 changes: 6 additions & 3 deletions src/clapy/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import inspect
import os
import re
from typing import List
from typing import List, Tuple


DIR_EXCLUSIONS = [r"__pycache__"]
Expand Down Expand Up @@ -67,7 +67,10 @@ def import_class_by_namespace(namespace: str) -> type:


@staticmethod
def get_all_classes(location, directory_exclusion_patterns, file_exclusion_patterns) -> List[(object, str)]:
def get_all_classes(
location: str,
directory_exclusion_patterns: List[str],
file_exclusion_patterns: List[str]) -> List[Tuple[object, str]]:
'''
Summary
-------
Expand All @@ -91,7 +94,7 @@ def get_all_classes(location, directory_exclusion_patterns, file_exclusion_patte
_Directories[:] = [_Dir for _Dir in _Directories if not re.match(_ExclusionPattern, _Dir)]

for _ExclusionPattern in file_exclusion_patterns + FILE_EXCLUSIONS:
_Files[:] = [_File for _File in _Files if not re.match(_ExclusionPattern, _Files)]
_Files[:] = [_File for _File in _Files if not re.match(_ExclusionPattern, _File)]

for _File in _Files:
_Namespace = _Root.replace('/', '.').lstrip(".") + "." + _File[:-3]
Expand Down
13 changes: 6 additions & 7 deletions src/clapy/dependency_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from .common import Common
from .engine import Engine, PipelineFactory, UseCaseInvoker
from .exceptions import DuplicateServiceError
from .generics import TServiceType
from .pipeline import IPipe
from .services import IPipelineFactory, IServiceProvider, IUseCaseInvoker

Expand All @@ -22,7 +21,7 @@ class DependencyInjectorServiceProvider(IServiceProvider):
def __init__(self):
self._container = containers.DeclarativeContainer()

def get_service(self, service: Type[TServiceType]) -> TServiceType:
def get_service(self, service: type) -> object:
'''
Summary
-------
Expand Down Expand Up @@ -53,9 +52,9 @@ def get_service(self, service: Type[TServiceType]) -> TServiceType:

def register_service(
self,
provider_method: Type,
concrete_type: Type[TServiceType],
interface_type: Optional[Type[TServiceType]] = None, *args) -> None:
provider_method: type,
concrete_type: type,
interface_type: Optional[type] = None, *args) -> None:
'''
Summary
-------
Expand Down Expand Up @@ -162,7 +161,7 @@ def construct_usecase_invoker(
self.register_service(providers.Factory, UseCaseInvoker, IUseCaseInvoker)
return self.get_service(IUseCaseInvoker)

def _try_generate_service_name(self, service: Type[TServiceType]) -> Tuple[str, bool]:
def _try_generate_service_name(self, service: type) -> Tuple[str, bool]:
'''
Summary
-------
Expand All @@ -185,7 +184,7 @@ def _try_generate_service_name(self, service: Type[TServiceType]) -> Tuple[str,

return _TypeMatch.group().replace('.', '_'), True

def _has_service(self, service: Type[TServiceType]) -> bool:
def _has_service(self, service: type) -> bool:
'''
Summary
-------
Expand Down
8 changes: 4 additions & 4 deletions src/clapy/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from .common import Common
from .exceptions import PipeConfigurationError
from .generics import TInputPort, TOutputPort
from .outputs import IOutputPort
from .pipeline import (InputPort, IPipe, PipeConfiguration,
PipeConfigurationOption)
from .services import IPipelineFactory, IServiceProvider, IUseCaseInvoker
Expand All @@ -18,7 +18,7 @@ def __init__(self, service_provider: IServiceProvider, usecase_registry: Dict[st

async def create_pipeline_async(
self,
input_port: TInputPort,
input_port: InputPort,
pipeline_configuration: List[PipeConfiguration]) -> List[Type[IPipe]]:
'''
Summary
Expand Down Expand Up @@ -77,8 +77,8 @@ def __init__(self, pipeline_factory: IPipelineFactory):

async def invoke_usecase_async(
self,
input_port: TInputPort,
output_port: TOutputPort,
input_port: InputPort,
output_port: IOutputPort,
pipeline_configuration: List[PipeConfiguration]) -> None:
'''
Summary
Expand Down
8 changes: 0 additions & 8 deletions src/clapy/generics.py

This file was deleted.

51 changes: 45 additions & 6 deletions src/clapy/outputs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
from abc import ABC, abstractmethod
from typing import Generic
from typing import Dict, List, Optional

from .generics import TAuthorisationFailure, TValidationFailure

class AuthorisationResult:
'''An authorisation result from an authorisation enforcer.'''

def __init__(self, reason: Optional[str] = None) -> None:
self.reason = reason


class ValidationResult:
'''A validation result from a validator.'''

def __init__(
self,
errors: Optional[Dict[str, List[str]]] = {},
summary: Optional[str] = None) -> None:
self.errors = errors
self.summary = summary

@classmethod
def from_error(cls, property: str, error_message: str) -> 'ValidationResult':
'''#TODO'''
instance = cls()
instance.add_error(property, error_message)
return instance

@classmethod
def from_summary(cls, summary: str) -> 'ValidationResult':
'''#TODO'''
instance = cls()
instance.summary = summary
return instance

def add_error(self, property: str, error_message: str) -> None:
'''#TODO'''
self.errors.setdefault(property.__name__, []).append(error_message)


class IOutputPort(ABC):
'''Marks a class as a use case output port.'''
pass


class IAuthenticationOutputPort(ABC):
Expand All @@ -13,19 +52,19 @@ async def present_unauthenticated_async() -> None:
pass


class IAuthorisationOutputPort(Generic[TAuthorisationFailure], ABC):
class IAuthorisationOutputPort(ABC):
'''An output port for when authorisation is required by the use case.'''

@abstractmethod
async def present_unauthorised_async(self, authorisation_failure: TAuthorisationFailure) -> None:
async def present_unauthorised_async(self, authorisation_failure: AuthorisationResult) -> None:
'''Presents an authorisation failure.'''
pass


class IValidationOutputPort(Generic[TValidationFailure], ABC):
class IValidationOutputPort(ABC):
'''An output port for when validation is required by the use case.'''

@abstractmethod
async def present_validation_failure_async(self, validation_failure: TValidationFailure) -> None:
async def present_validation_failure_async(self, validation_failure: ValidationResult) -> None:
'''Presents a validation failure.'''
pass
62 changes: 31 additions & 31 deletions src/clapy/pipeline.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
from abc import ABC, abstractmethod
from enum import Enum
from typing import Any, Coroutine, Generic, NamedTuple, Type, Union
from typing import NamedTuple, Type

from .outputs import IValidationOutputPort
from .generics import TInputPort, TOutputPort
from .outputs import IOutputPort, IValidationOutputPort, ValidationResult


class IPipe(Generic[TInputPort, TOutputPort], ABC):
'''Marks a class as a pipe. A pipe is a class that must have an execution method and a priority.'''
class InputPort:
'''Marks a class as an input port (not an implementation of IPipe). The entry point for all use cases.'''
pass


class IPipe(InputPort, IOutputPort, ABC):
'''Marks a class as a pipe. A pipe is a class that has an execution method and reports on failures.'''

def __init__(self) -> None:
self._has_failures = False

@abstractmethod
async def execute_async(self, input_port: TInputPort, output_port: TOutputPort) -> Union[Coroutine, None]:
async def execute_async(self, input_port: InputPort, output_port: IOutputPort) -> None:
'''
Summary
-------
Defines the behaviour of the pipe when executed. Must return either a coroutine
function (an output port method), or no result.
Defines the behaviour of the pipe when executed.
Parameters
----------
`input_port` The input of the use case to be processed\n
`output_port` The interface containing output methods to return as a result of the pipe's execution
Returns
-------
`Coroutine` (a method of the output port) if the pipe has a result to return, otherwise `None`.
`output_port` The interface containing methods to output the result of the pipe's execution
'''
pass

@property
def has_failures(self) -> bool:
'''Determines whether or not a failure has occurred during the pipe's execution.'''
return self._has_failures

@has_failures.setter
Expand All @@ -48,7 +48,7 @@ class PipeConfigurationOption(Enum):
'''If found from searching the use case pipes, the pipe will be added, otherwise it is ignored.'''

INSERT = "INSERT"
'''Will insert the provided pipe at the specified location.'''
'''Will insert the pipe at the specified location, regardless of its presence within the defined used case.'''


class PipeConfiguration(NamedTuple):
Expand All @@ -57,8 +57,10 @@ class PipeConfiguration(NamedTuple):
Attributes:
type (Type[IPipe]): The type of the pipe.
option (PipeConfigurationOption): The configuration option for the pipe.
TODO
option (PipeConfigurationOption): The inclusion option for the pipe. Defaults to
`PipeConfigurationOption.DEFAULT`.
should_ignore_failures (bool): If true, will tell the invoker to continue the pipeline
regardless of failures. Defaults to `false`.
'''
type: Type[IPipe]
option: PipeConfigurationOption = PipeConfigurationOption.DEFAULT
Expand All @@ -81,11 +83,6 @@ class EntityExistenceChecker(IPipe):
pass


class InputPort:
'''Marks a class as an input port (not an implementation of IPipe). The entry point for all use cases.'''
pass


class InputPortValidator(IPipe):
'''Marks a class as an input port validator pipe. Used to enforce integrity and correctness of input data.'''
pass
Expand All @@ -103,25 +100,28 @@ class PersistenceRuleValidator(IPipe):


def required(func):
'''#TODO: docs'''
'''Marks a property on an InputPort as required. Used alongside the `RequiredInputValidator`
pipe, the `required` decorator enforces values to be supplied to use cases.'''
def wrapper(self):
return func(self)
return wrapper


class RequiredInputValidator(IPipe):
#TODO: Docs
'''A validation pipe, used to check if any required inputs from the use case's InputPort have
not been given a value. Required inputs are identified via the `required` decorator.'''

async def execute_async(self, input_port: Any, output_port: Any) -> Coroutine[Any, Any, Union[Coroutine, None]]:
properties = [(attr, getattr(input_port.__class__, attr)) for attr in dir(input_port.__class__)
async def execute_async(self, input_port: InputPort, output_port: IValidationOutputPort) -> None:
_Properties = [(attr, getattr(input_port.__class__, attr)) for attr in dir(input_port.__class__)
if isinstance(getattr(input_port.__class__, attr), property)]

fails = []
_MissingInputs = []

for name, prop in properties:
if prop.__get__(input_port) is None:
fails.append(name)
for _Name, _Property in _Properties:
if _Property.__get__(input_port) is None:
_MissingInputs.append(_Name)

if issubclass(type(output_port), IValidationOutputPort) and fails:
await output_port.present_validation_failure_async(f"Required inputs must have a value: {', '.join(fails)}")
if issubclass(type(output_port), IValidationOutputPort) and _MissingInputs:
await output_port.present_validation_failure_async(
ValidationResult.from_summary(f"Required inputs must have a value: {', '.join(_MissingInputs)}"))
self.has_failures = True
16 changes: 8 additions & 8 deletions src/clapy/services.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from abc import ABC, abstractmethod
from typing import List, Type

from .generics import TInputPort, TOutputPort, TServiceType
from .pipeline import IPipe, PipeConfiguration
from .outputs import IOutputPort
from .pipeline import IPipe, InputPort, PipeConfiguration


class IPipelineFactory(ABC):
Expand All @@ -11,7 +11,7 @@ class IPipelineFactory(ABC):
@abstractmethod
async def create_pipeline_async(
self,
input_port: TInputPort,
input_port: InputPort,
pipeline_configuration: List[PipeConfiguration]) -> List[Type[IPipe]]:
'''
Summary
Expand All @@ -36,7 +36,7 @@ class IServiceProvider(ABC):
'''A generic interface for getting services from a dependency injection container.'''

@abstractmethod
def get_service(self, service: Type[TServiceType]) -> TServiceType:
def get_service(self, service: type) -> object:
'''
Summary
-------
Expand All @@ -55,19 +55,19 @@ def get_service(self, service: Type[TServiceType]) -> TServiceType:


class IUseCaseInvoker(ABC):
'''The main engine of Clapy. Handles the invocation of use case pipelines and the execution of resulting actions.'''
'''The main engine of Clapy. Handles the invocation of use case pipelines.'''

@abstractmethod
async def invoke_usecase_async(
self,
input_port: TInputPort,
output_port: TOutputPort,
input_port: InputPort,
output_port: IOutputPort,
pipeline_configuration: List[Type[IPipe]]) -> None:
'''
Summary
-------
Performs the invocation of a use case with the provided input and output ports. Will stop
invocation on receival of a coroutine result, or if the pipeline's pipes are exhausted.
the pipeline if the pipeline's pipes are exhausted, or on pipe failure unless configured to ignore.
Parameters
----------
Expand Down

0 comments on commit 70a95ea

Please sign in to comment.