def test_event_api(monkeypatch, mock_api_spec, mock_api_app_config): # noqa: F811 monkeypatch.setattr(api, 'spec', mock_api_spec) spec = api.event_api(description="Test app api", payload=(str, "Payload"), query_args=[('arg1', Optional[int], "Argument 1")], responses={200: (MockData, "MockData result") })(mock_app_api_get, app_config=mock_api_app_config, event_name='mock-app-api-get', plugin=None) assert spec['description'] == \ mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['get']['description'] assert spec['parameters'][0] == \ mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['get']['parameters'][0] assert spec['responses'] == \ mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['get']['responses']
def test_event_api_post(monkeypatch, mock_api_spec, mock_api_app_config): # noqa: F811 monkeypatch.setattr(api, 'spec', mock_api_spec) mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['post']['parameters'][0]['description'] = \ 'arg1' mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['post']['requestBody']['description'] = \ 'MockData' spec = api.event_api(description="Description Test app api part 2", payload=MockData, query_args=['arg1'], responses={200: int})(mock_app_api_post, app_config=mock_api_app_config, event_name='mock-app-api-post', plugin=None) assert spec['summary'] == \ mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['post']['summary'] assert spec['description'] == \ mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['post']['description'] assert spec['parameters'][0] == \ mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['post']['parameters'][0] assert spec['requestBody'] == \ mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['post']['requestBody'] assert spec['responses'] == \ mock_api_spec['paths']['/api/mock-app-api/test/mock-app-api']['post']['responses']
'runtime_apps', 'config_graph', 'cytoscape_data'), 'build_visualization' ] @dataobject @dataclass class EventsGraphResult: runtime_apps: RuntimeApps graph: CytoscapeGraph options: VisualizationOptions __api__ = event_api( summary="App Visualizer: Events Graph Data", description="App Visualizer: Events Graph Data", query_args=visualization_options_api_args(), responses={200: (EventsGraphResult, "Graph Data with applied Live Stats")}) async def runtime_apps(collector: Collector, context: EventContext) -> RuntimeApps: """ Extract current runtime app_config objects """ options: VisualizationOptions = await collector['payload'] return await get_runtime_apps(context, expand_events=options.expanded_view) def _filter_apps(runtime_info: RuntimeAppInfo, options: VisualizationOptions) -> bool:
from random import randrange from typing import Union, Optional from datetime import datetime, timezone from hopeit.app.api import event_api from hopeit.app.context import EventContext, PostprocessHook from hopeit.app.logger import app_extra_logger from hopeit.fs_storage import FileStorage from model import Something, StatusType, Status, SomethingNotFound __steps__ = ['load', 'update_status_history'] __api__ = event_api(query_args=[('item_id', str, 'Item Id to read')], responses={ 200: (Something, "Something object returned when found"), 404: (SomethingNotFound, "Information about not found object") }) logger, extra = app_extra_logger() fs: Optional[FileStorage] = None async def __init_event__(context): global fs if fs is None: fs = FileStorage(path=str(context.env['fs']['data_path'])) async def load(
Invalidates previous refresh cookies. """ from hopeit.app.api import event_api, app_base_route_name from hopeit.app.context import EventContext, PostprocessHook from hopeit.app.logger import app_logger from hopeit.toolkit.auth import AuthType from hopeit.app.errors import Unauthorized logger = app_logger() __steps__ = ['logout'] __api__ = event_api(summary="Basic Auth: Logout", responses={ 200: (str, "Logged out message."), 401: (Unauthorized.ErrorInfo, "Login failed, invalid credentials or not logged in.") }) async def logout(payload: None, context: EventContext): assert context.auth_info['allowed'] if not context.auth_info['auth_type'] == AuthType.REFRESH: raise Unauthorized('Invalid authorization') async def __postprocess__(payload: None, context: EventContext, *, response: PostprocessHook) -> str: response.del_cookie(name=f"{context.app_key}.refresh", path=f"{app_base_route_name(context.app)}/",
from hopeit.app.events import Spawn, SHUFFLE from hopeit.app.logger import app_extra_logger from hopeit.fs_storage import FileStorage, FileStorageSettings from model import Something, SomethingStored, Status, StatusType logger, extra = app_extra_logger() __steps__ = [ 'fork_something', SHUFFLE, 'process_first_part', 'process_second_part', SHUFFLE, 'update_status', 'save' ] __api__ = event_api( summary="Simple Example: Parallelize Event", payload=( Something, "Something object to forked and submitted to be processed concurrently" ), responses={200: (str, 'events submitted successfully message')}) @dataobject @dataclass class FirstPart: data: Something @dataobject @dataclass class SecondPart: data: Something
""" from hopeit.app.api import event_api from hopeit.app.logger import app_extra_logger from hopeit.app.context import EventContext, PreprocessHook from hopeit.dataobjects import BinaryAttachment from hopeit.dataobjects.payload import Payload __steps__ = ['entry_point'] from mock_app import MockData logger, extra = app_extra_logger() __api__ = event_api(description="Description Test app api part 2", fields=([('field1', str, "Field 1"), ('field2', MockData, "Field 2 json"), ('file', BinaryAttachment, "Upload file")]), query_args=[('arg1', str, "Argument 1")], responses={200: int}) async def __preprocess__(payload: None, context: EventContext, request: PreprocessHook) -> MockData: args = await request.parsed_args() data = Payload.parse_form_field(args['field2'], MockData) return MockData( value=f"field1:{args['field1']} field2:{data.value} file:{args['file']}" ) def entry_point(payload: MockData, context: EventContext, arg1: str) -> int: logger.info(context, "mock_app_api_multipart.entry_point")
""" Collected items. Need a dataclass in order to publish to stream """ items: List[Something] __steps__ = [ collector_step(payload=ItemsInfo).gather( 'load_first', 'load_second', 'combine' ), 'result', SHUFFLE, 'spawn' ] __api__ = event_api( summary="Simple Example: Collect and Spawn", payload=(ItemsInfo, "Items to read concurrently"), responses={ 200: (int, "Number of items spawned (0,1 or 2)"), } ) logger, extra = app_extra_logger() fs: Optional[FileStorage] = None async def __init_event__(context): global fs if fs is None: settings: FileStorageSettings = context.settings( key="fs_storage", datatype=FileStorageSettings ) fs = FileStorage.with_settings(settings)
from hopeit.app.logger import app_extra_logger from hopeit.config_manager import RuntimeApps from hopeit.app.api import event_api from hopeit.config_manager.runtime import get_in_process_config logger, extra = app_extra_logger() __steps__ = ['get_apps_config'] __api__ = event_api( summary="Config Manager: Runtime Apps Config", description= "Returns the runtime config for the Apps running on this server", query_args=[("url", Optional[str], "URL used to reach this server, informative"), ("expand_events", Optional[bool], "Retrieve expanded effective events from event steps")], responses={ 200: (RuntimeApps, "Config info about running apps in current process"), }) async def get_apps_config(payload: None, context: EventContext, *, url: str = "in-process", expand_events: bool = False) -> RuntimeApps: return get_in_process_config(url, expand_events=expand_events is True or expand_events == "true")
""" from datetime import datetime, timezone from hopeit.app.api import event_api from hopeit.app.context import EventContext from hopeit.app.logger import app_extra_logger from model import Something, Status, StatusType __steps__ = ['stream_event'] logger, extra = app_extra_logger() __api__ = event_api( summary="Simple Example: Something Event", payload=(Something, "Something object to submitted to stream"), responses={ 200: (Something, 'Updated Something object with status submitted to string') }) def stream_event(payload: Something, context: EventContext) -> Something: logger.info(context, "streaming event", extra=extra(something_id=payload.id)) if payload.status: payload.history.append(payload.status) payload.status = Status(ts=datetime.now(tz=timezone.utc), type=StatusType.SUBMITTED) return payload
from hopeit.app.api import event_api from hopeit.dataobjects import BinaryDownload from hopeit.app.context import EventContext, PostprocessHook __steps__ = ['create_file'] @dataclass class ImagePng(BinaryDownload): content_type = 'image/png' file_name: str file_path: str __api__ = event_api(summary="Test app file response", query_args=[('file_name', str, "File Name")], responses={200: (ImagePng, "")}) async def create_file(payload: None, context: EventContext, file_name: str) -> ImagePng: path = Path("/tmp") / file_name img_file = ImagePng(file_name=file_name, file_path=path) path = path.absolute() with open(path, 'w') as f: f.write(b'mock_file_response test file_response') return img_file async def __postprocess__(img_file: ImagePng, context: EventContext, *, response: PostprocessHook) -> str:
from typing import Optional from hopeit.app.api import event_api from hopeit.app.context import EventContext, PostprocessHook from hopeit.app.events import Spawn, SHUFFLE from hopeit.app.logger import app_extra_logger from hopeit.fs_storage import FileStorage from model import Something, SomethingStored, Status, StatusType logger, extra = app_extra_logger() __steps__ = ['spawn_many_events', SHUFFLE, 'update_status', 'save'] __api__ = event_api( summary="Simple Example: Spawn Event", payload=(Something, "Something object to submitted several times to stream"), responses={200: (str, 'events submitted successfully message')}) fs: Optional[FileStorage] = None async def __init_event__(context): global fs if fs is None: fs = FileStorage(path=str(context.env['fs']['data_path'])) async def spawn_many_events(payload: Something, context: EventContext) -> Spawn[Something]: """
-------------------------------------------------------------------- Loads Something from disk """ import asyncio from random import randrange from hopeit.app.api import event_api from hopeit.app.context import EventContext from hopeit.app.logger import app_extra_logger from model import Something, User __steps__ = ['create'] __api__ = event_api( query_args=[('item_id', str, 'Item Id to read')], responses={200: (Something, "Something object returned when found")}) logger, extra = app_extra_logger() async def create(payload: None, context: EventContext, *, item_id: str, update_status: bool = False) -> Something: """ Loads json file from filesystem as `Something` instance :param payload: unused :param context: EventContext
""" from typing import Optional, Union from hopeit.app.context import EventContext, PostprocessHook from hopeit.app.api import event_api from hopeit.app.logger import app_extra_logger from .data_model import MyData, Status, MyMessage logger, extra = app_extra_logger() __steps__ = ['create_message', 'validate'] __api__ = event_api(payload=(MyData, "data received"), responses={ 200: (MyMessage, "message submitted to process"), 400: (str, "invalid message error") }) async def create_message(payload: MyData, context: EventContext) -> MyMessage: """ Creates MyMessage objects from the received text in MyData payload """ logger.info(context, "Received data", extra=extra(length=len(payload.text))) message = MyMessage(payload.text, Status.NEW) return message
from typing import Optional, List from datetime import datetime, date from hopeit.app.api import event_api from hopeit.app.logger import app_extra_logger from hopeit.app.context import EventContext from mock_app import MockData logger, extra = app_extra_logger() __steps__ = ['entry_point'] __api__ = event_api(summary="Test app api query args", description="Description of Test app queryargs", query_args=[('arg_str', Optional[str], "str argument"), ('arg_int', Optional[int], "int argument"), ('arg_float', Optional[float], "float argument"), ('arg_date', Optional[date], "date argument"), ('arg_datetime', Optional[datetime], "datetime argument")], responses={200: (List[MockData], "MockData result")}) def entry_point(payload: None, context: EventContext, *, arg_str: Optional[str] = None, arg_int: Optional[int] = None, arg_float: Optional[float] = None, arg_date: Optional[date] = None, arg_datetime: Optional[datetime] = None) -> List[MockData]:
from typing import Optional, Union from hopeit.app.api import event_api from hopeit.app.logger import app_extra_logger from hopeit.app.context import EventContext, PreprocessHook from hopeit.fs_storage import FileStorage, FileStorageSettings from model import Something, User, SomethingParams from common.validation import validate __steps__ = ['create_something', 'save'] __api__ = event_api(summary="Simple Example: Save Something", payload=(SomethingParams, "provide `id` and `user` to create Something"), responses={ 200: (str, 'path where object is saved'), 400: (str, 'bad request reason') }) logger, extra = app_extra_logger() fs: Optional[FileStorage] = None async def __init_event__(context): global fs if fs is None: settings: FileStorageSettings = context.settings( key="fs_storage", datatype=FileStorageSettings) fs = FileStorage.with_settings(settings)
from hopeit.app.api import event_api from hopeit.app.context import EventContext, PostprocessHook from hopeit.dataobjects import BinaryDownload __steps__ = ['get_streamed_data'] @dataclass class SomeFile(BinaryDownload): file_name: str __api__ = event_api( query_args=[("file_name", str, "expected return file name")], responses={ 200: (SomeFile, "Return content with filename=`file_name`") } ) async def get_streamed_data(payload: None, context: EventContext, *, file_name: str) -> SomeFile: """ Prepare output file name to be streamd """ return SomeFile(file_name=file_name) async def __postprocess__(file: SomeFile, context: EventContext, response: PostprocessHook) -> SomeFile: """ Stream 50 MB of binary content: """
from hopeit.server.names import route_name from hopeit.app.api import event_api from hopeit.dataobjects import dataclass, dataobject from hopeit.config_manager import RuntimeApps from hopeit.apps_visualizer.apps import get_runtime_apps from hopeit.apps_visualizer.site.visualization import VisualizationOptions, \ visualization_options, visualization_options_api_args # noqa: F401 __steps__ = ['visualization_options', 'runtime_apps_config'] __api__ = event_api( summary="App Visualizer: Site", description="[Click here to open Events Graph](/ops/apps-visualizer)", query_args=visualization_options_api_args(), responses={200: (str, "HTML page with Events Graph")}) _dir_path = Path(os.path.dirname(os.path.realpath(__file__))) @dataobject @dataclass class RuntimeAppsConfig: runtime_apps: RuntimeApps options: VisualizationOptions async def runtime_apps_config(options: VisualizationOptions, context: EventContext) -> RuntimeAppsConfig:
from hopeit.app.logger import app_extra_logger from hopeit.app.context import EventContext, PreprocessHook from hopeit.dataobjects import dataobject, BinaryAttachment from hopeit.dataobjects.jsonify import Json from hopeit.toolkit.web import save_multipart_attachment from model import Something __steps__ = ['create_items'] __api__ = event_api(summary="Simple Example: Multipart Upload files", description="Upload files using Multipart form request", query_args=[('something_id', str)], fields=[('id', str), ('user', str), ('attachment', BinaryAttachment), ('object', Something)], responses={ 200: (List[Something], 'list of created Something objects'), 400: (str, "Missing or invalid fields") }) logger, extra = app_extra_logger() @dataobject @dataclass class UploadedFile: file_id: str file_name: str saved_path: str
""" from typing import Optional, List from hopeit.app.api import event_api from hopeit.app.context import EventContext from hopeit.app.logger import app_extra_logger from hopeit.fs_storage import FileStorage, FileStorageSettings from model import Something __steps__ = ['load_all'] __api__ = event_api( summary="Simple Example: List Objects", query_args=[ ('wildcard', Optional[str], "Wildcard to filter objects by name") ], responses={ 200: List[Something] } ) logger, extra = app_extra_logger() fs: Optional[FileStorage] = None async def __init_event__(context): global fs if fs is None: settings: FileStorageSettings = context.settings( key="fs_storage", datatype=FileStorageSettings )
from hopeit.app.logger import app_extra_logger from hopeit.app.context import EventContext, PostprocessHook __steps__ = ['find_image'] @dataclass class ImagePng(BinaryDownload): content_type = 'image/png' file_name: str file_path: str __api__ = event_api(query_args=[("file_name", str, "return file name, try with something.png")], responses={ 200: (ImagePng, "Return requested image file"), 400: (str, "Image file not found") }) logger, extra = app_extra_logger() async def find_image(payload: None, context: EventContext, *, file_name: str) -> ImagePng: """ Find image file """ src_file_path = f"./apps/examples/simple-example/resources/{file_name}" tgt_file_path = os.path.join(str(context.env['storage']['base_path']), file_name) try:
from hopeit.app.api import event_api from hopeit.basic_auth import ContextUserInfo, AuthInfo, AuthInfoExtended, authorize, set_refresh_token from hopeit.app.context import EventContext, PostprocessHook from hopeit.app.logger import app_logger from hopeit.toolkit.auth import AuthType from hopeit.app.errors import Unauthorized logger = app_logger() __steps__ = ['refresh'] __api__ = event_api( summary="Basic Auth: Refresh", responses={ 200: (AuthInfo, "Refreshed authentication information to be used for further API calls" ), 401: (Unauthorized.ErrorInfo, "Login failed, invalid credentials. An http-cookie is expected") }) async def refresh(payload: None, context: EventContext) -> AuthInfoExtended: """ Returns a new access and refresh tokens, from a request containing a valid refresh token. """ assert context.auth_info['allowed'] now = datetime.now().astimezone(timezone.utc) if context.auth_info['auth_type'] == AuthType.REFRESH: user_info = ContextUserInfo( id=context.auth_info['payload']['id'],
from hopeit.app.api import event_api from hopeit.app.context import EventContext, PostprocessHook from hopeit.app.logger import app_extra_logger from hopeit.fs_storage import FileStorage, FileStorageSettings from common.validation import validate from model import Something, Status, SomethingNotFound __steps__ = ['load', 'save_with_updated_status'] __api__ = event_api(summary="Simple Example: Query Something Extended", query_args=[('item_id', str, 'Item Id to read'), ('partition_key', str, 'Partition folder in `YYYY/MM/DD/HH` format') ], payload=(Status, "Status change for the retrieved object"), responses={ 200: (Something, "Something object returned when found"), 404: (SomethingNotFound, "Information about not found object") }) logger, extra = app_extra_logger() fs: Optional[FileStorage] = None async def __init_event__(context): global fs if fs is None: settings: FileStorageSettings = context.settings( key="fs_storage", datatype=FileStorageSettings)
""" Cluster Apps Config ---------------------------------------------------------------------------------------- Handle remote access to runtime configuration from a group of hosts running hopeit.engine """ from hopeit.app.context import EventContext from hopeit.app.logger import app_extra_logger from hopeit.app.api import event_api from hopeit.config_manager import RuntimeApps, client logger, extra = app_extra_logger() __steps__ = ['get_hosts_apps_config'] __api__ = event_api( summary="Config Manager: Cluster Apps Config", description="Handle remote access to runtime configuration for a group of hosts", query_args=[("hosts", str, "Comma-separated list of http://host:port strings")], responses={ 200: (RuntimeApps, "Combined config info about running apps in provided list of hosts") } ) async def get_hosts_apps_config(payload: None, context: EventContext, *, hosts: str) -> RuntimeApps: return await client.get_apps_config(hosts, context)
-------------------------------------------------------------------- Creates and saves Something """ from typing import Optional from hopeit.app.api import event_api from hopeit.app.logger import app_extra_logger from hopeit.app.context import EventContext from hopeit.fs_storage import FileStorage, FileStorageSettings from model import Something, User, SomethingParams __steps__ = ['create_something', 'save'] __api__ = event_api(payload=(SomethingParams, "provide `id` and `user` to create Something"), responses={200: (str, 'path where object is saved')}) logger, extra = app_extra_logger() fs: Optional[FileStorage] = None rnd: int = 0 async def __init_event__(context): global fs if fs is None: settings: FileStorageSettings = context.settings( key="fs_storage", datatype=FileStorageSettings) fs = FileStorage.with_settings(settings)
from hopeit.app.api import event_api from hopeit.basic_auth import ContextUserInfo, AuthInfo, AuthInfoExtended, authorize, set_refresh_token from hopeit.app.context import EventContext, PostprocessHook from hopeit.app.logger import app_logger from hopeit.toolkit.auth import AuthType from hopeit.app.errors import Unauthorized logger = app_logger() __steps__ = ['login'] __api__ = event_api( summary="Basic Auth: Login", responses={ 200: (AuthInfo, "Authentication information to be used for further API calls"), 401: (Unauthorized.ErrorInfo, "Login failed, invalid credentials") }) async def login(payload: None, context: EventContext) -> AuthInfoExtended: """ Returns a new access and refresh token for a set of given basic-auth credentials """ assert context.auth_info['allowed'] now = datetime.now(tz=timezone.utc) if context.auth_info['auth_type'] == AuthType.BASIC: data = base64.b64decode(context.auth_info['payload'].encode()).decode() user_info = ContextUserInfo( id='id',
""" Basic Auth: Decode -------------------------------------------------------------------- Returns decoded auth info """ from hopeit.app.api import event_api from hopeit.basic_auth import ContextUserInfo from hopeit.app.context import EventContext from hopeit.app.logger import app_logger logger = app_logger() __steps__ = ['decode'] __api__ = event_api( summary="Basic Auth: Decode", responses={ 200: (ContextUserInfo, "Information extracted from token") } ) async def decode(payload: None, context: EventContext) -> ContextUserInfo: token_info = context.auth_info['payload'] return ContextUserInfo( id=token_info['id'], user=token_info['user'], email=token_info['email'] )
from hopeit.dataobjects import dataobject __steps__ = ['step1'] @dataobject @dataclass class MyObject: text: str length: int __api__ = event_api( query_args=[ ('payload', str, "provide a 'string' to create 'MyObject'"), ('number', int, "number to be added to the 'length' of the payload of MyObject") ], responses={ 200: (MyObject, "MyObject where name is the received string uppercased and number its length" ) }) async def step1(payload: str, context: EventContext, number: str) -> MyObject: text = payload.upper() length = len(payload) + int(number) return MyObject(text, length)
""" NO TITLE HERE, Title in __api__ Description of Test app api list """ from typing import Optional, List from hopeit.app.api import event_api from hopeit.app.logger import app_extra_logger from hopeit.app.context import EventContext from mock_app import MockData logger, extra = app_extra_logger() __steps__ = ['entry_point'] __api__ = event_api(summary="Test app api list", query_args=[('arg1', Optional[int], "Argument 1")], responses={200: (List[MockData], "MockData result")}) def entry_point(payload: None, context: EventContext, *, arg1: Optional[int] = None) -> List[MockData]: logger.info(context, "mock_app_api_get_list.entry_point") return [MockData(f"get-{arg1}")]
from hopeit.app.events import collector_step from hopeit.app.logger import app_extra_logger from hopeit.server.collector import Collector from hopeit.fs_storage import FileStorage from model import ItemsInfo, Something, SomethingNotFound __steps__ = [ collector_step(payload=ItemsInfo).gather('load_first', 'load_second', 'combine'), 'result' ] __api__ = event_api( summary="Simple Example: Query Concurrently", payload=(ItemsInfo, "Items to read concurrently"), responses={ 200: (List[Something], "List of one or two Something objects returned found, empty list if none is found" ), }) logger, extra = app_extra_logger() fs: Optional[FileStorage] = None async def __init_event__(context): global fs if fs is None: fs = FileStorage(path=str(context.env['fs']['data_path']))