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
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()
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() }
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_
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
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()