예제 #1
0
def test_operation_signature():
    """ Test how signature reading works """

    # An operation with pure arguments, no DI involved

    @operation()
    def f(a, b: int, c=1, d: int = 2) -> str:
        pass

    s = operation.get_from(f).signature
    assert s.provided_arguments == {}
    assert s.arguments == {'a': Any, 'b': int, 'c': Any, 'd': int}
    assert s.argument_defaults == {'c': 1, 'd': 2}
    assert s.return_type == str

    # An operation with DI.
    # Argument 'c' goes into the "provided" list and leaves all other lists.

    @operation()
    @di.kwargs(c='something')
    def f(a, b: int, c=1, d: int = 2) -> str:
        pass

    s = operation.get_from(f).signature
    assert s.provided_arguments == {'c': Any}  # provided. 'c' is removed from all other lists
    assert s.arguments == {'a': Any, 'b': int, 'd': int}
    assert s.argument_defaults == {'d': 2}
    assert s.return_type == str
예제 #2
0
def test_operations():
    """ Test a few operations """

    def main():
        # Try out @operation-decorated business logic with an Injector DI
        with di.Injector() as root:
            root.provide(authenticated_user, authenticated_user)

            # Call some operations
            assert call_operation(root, 'whoami') == 'kolypto'
            assert call_operation(root, 'list_users') == []
            assert call_operation(root, 'create_user', login='******') == None
            assert call_operation(root, 'list_users') == [User(login='******', created_by=User(login='******'))]
            assert call_operation(root, 'delete_user', index=0) == None
            assert call_operation(root, 'list_users') == []

    # Example dependency
    @di.signature()
    def authenticated_user():
        return User(login='******')

    # Example operation
    @operation()
    @di.kwargs(current_user=authenticated_user)
    def whoami(current_user: User):
        return current_user.login

    users = []

    # Example CRUD operations
    @operation()
    @di.kwargs(current_user=authenticated_user)
    def create_user(current_user: User, login: str):
        users.append(User(login=login, created_by=current_user))

    @operation()
    @di.kwargs()
    def list_users():
        return users

    @operation()
    @di.kwargs()
    def delete_user(index: int):
        del users[index]

    # Example router
    all_operations = [whoami, create_user, list_users, delete_user]

    operations_map = {
        # Operation id mapped to operation func
        operation.get_from(op).operation_id: op
        for op in all_operations
    }

    def call_operation(injector: di.Injector, operation_name: str, **kwargs):
        operation_func = operations_map[operation_name]  # might as well check the arguments here
        return injector.invoke(operation_func, **kwargs)

    # Test
    main()
예제 #3
0
def test_operation_doc_string(func: Callable, expected_doc: dict):
    """ Test how operation docstrings are read """
    actual_doc = operation.get_from(func).doc

    # Test: function doc
    expected_function_doc = expected_doc.get('function', None)
    if expected_function_doc is None:
        assert actual_doc.function is None
    else:
        assert actual_doc.function.summary == expected_function_doc['summary']
        assert actual_doc.function.description == expected_function_doc[
            'description']

    # Test: result doc
    expected_result_doc = expected_doc.get('result', None)
    if expected_result_doc is None:
        assert actual_doc.result is None
    else:
        assert actual_doc.result.summary == expected_result_doc['summary']
        assert actual_doc.result.description == expected_result_doc[
            'description']

    # Test: deprecated doc
    expected_deprecated_doc = expected_doc.get('deprecated', None)
    if expected_deprecated_doc is None:
        assert actual_doc.deprecated is None
    else:
        assert actual_doc.deprecated.version == expected_deprecated_doc[
            'version']
        assert actual_doc.deprecated.summary == expected_deprecated_doc[
            'summary']
        assert actual_doc.deprecated.description == expected_deprecated_doc[
            'description']

    # Test: parameters doc
    expected_parameters_doc = expected_doc.get('parameters', {})
    assert expected_parameters_doc == {
        param_name: {
            'name': param_name,
            'summary': param_doc.summary,
            'description': param_doc.description
        }
        for param_name, param_doc in actual_doc.parameters.items()
    }

    # Test: errors doc
    expected_errors_doc = expected_doc.get('errors', {})
    assert expected_errors_doc == {
        error_type: {
            'error': error_doc.error,
            'summary': error_doc.summary,
            'description': error_doc.description
        }
        for error_type, error_doc in actual_doc.errors.items()
    }
예제 #4
0
    def register_class_operations(self, class_: type, *, prefix: str = None, tags: List[str] = None) -> type:
        """ Register class-based operations """
        # Get the class operation itself
        class_op = operation.get_from(class_)
        assert class_op is not None, f'Class {class_} must be decorated with @operation'

        # List its sub-operations
        for func_op in operation.all_decorated_from(class_, inherited=True):
            try:
                self._register_operation(func_op, class_op, prefix=prefix, tags=tags)
            except Exception as e:
                raise ValueError(f"Error registering @operation {func_op}") from e

        # Done
        return class_
예제 #5
0
    def register_func_operation(self, func: Callable, *, prefix: str = None, tags: List[str] = None) -> Callable:
        """ Register a single function-operation

        Args:
            func: The function to register as an operation
        """
        # Get the operation
        func_op = operation.get_from(func)
        assert func_op is not None, f'Function {func} must be decorated with @operation'

        # Register
        try:
            self._register_operation(func_op, prefix=prefix, tags=tags)
        except Exception as e:
            raise ValueError(f"Error registering @operation {func_op}") from e

        # Done
        return func
예제 #6
0
def test_class_based_operation():
    """ Test operations defined as a class """

    def main():
        # Try out @operation-decorated business logic with an Injector DI and class-based CRUD
        with di.Injector() as root:
            root.provide(authenticated_user, authenticated_user)

            # Call some operations
            assert call_operation(root, 'whoami') == 'kolypto'
            assert call_operation(root, 'user/list') == []
            assert call_operation(root, 'user/create', login='******') == None
            assert call_operation(root, 'user/list') == [User(login='******', created_by=User(login='******'))]
            assert call_operation(root, 'user/delete', index=0) == None
            assert call_operation(root, 'user/list') == []

    # Example dependency
    @di.signature()
    def authenticated_user():
        return User(login='******')

    # Example flat operation
    @operation()
    @di.kwargs(current_user=authenticated_user)
    def whoami(current_user: User):
        return current_user.login

    # Example class-based CRUD
    @operation('user')
    @di.kwargs(current_user=authenticated_user)
    class UserCrud:
        _db = []

        def __init__(self, current_user):
            self.current_user = current_user

        @operation()
        @di.signature()
        def list(self):
            return self._db

        @operation()
        @di.signature()
        def create(self, login: str):
            self._db.append(User(login=login, created_by=self.current_user))

        @operation()
        @di.signature()
        def delete(self, index: int):
            del self._db[index]

    # Example router
    flat_operations = [whoami]
    class_based_operations = [UserCrud]

    flat_operations_map = {
        # Flat operations are trivial
        operation.get_from(op).operation_id: op
        for op in flat_operations
    }

    class_based_operations_map = {
        # Class-based operations are not
        operation.get_from(cls).operation_id + '/' + op.operation_id: (cls, op.func_name)
        for cls in class_based_operations
        for op in operation.all_decorated_from(cls)
    }

    def call_operation(injector: di.Injector, operation_name: str, **kwargs):
        # Flat operations. Go straight ahead
        if operation_name in flat_operations_map:
            operation_func = flat_operations_map[operation_name]
            return injector.invoke(operation_func, **kwargs)
        # Class-based operations
        elif operation_name in class_based_operations_map:
            cls, method_name = class_based_operations_map[operation_name]

            # Construct, invoke
            obj = injector.invoke(cls)
            return injector.invoke(
                getattr(obj, method_name),
                **kwargs
            )

    # Test
    main()