From 4926c53390e6897f318e5edf901027f5540d438f Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 11 Sep 2023 09:28:25 -0400 Subject: [PATCH 1/3] Added a README on invoking the REST endpoint. Signed-off-by: dblock --- samples/hello/README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/samples/hello/README.md b/samples/hello/README.md index 8a1cc40..5a6fb0d 100644 --- a/samples/hello/README.md +++ b/samples/hello/README.md @@ -1,5 +1,13 @@ +- [Hello World](#hello-world) + - [Start OpenSearch](#start-opensearch) + - [Start the Extension](#start-the-extension) + - [Register the Extension](#register-the-extension) + - [Call an Extension API](#call-an-extension-api) + # Hello World +This sample implements an extension that exposes a REST API. + ### Start OpenSearch Enable the `opensearch.experimental.feature.extensions.enabled` experimental feature in either way described in [the developer guide](https://github.com/opensearch-project/opensearch-sdk-java/blob/main/DEVELOPER_GUIDE.md#enable-the-extensions-feature-flag). @@ -80,4 +88,18 @@ INFO:root:< prefix=b'ES', version=2.10.0.99, type=['request'], message=50 byte(s INFO:root:> prefix=b'ES', version=2.10.0.99, type=['response'], message=179 byte(s), id=9, ctx=req={}, res={}, id=hello-world, version=3.0.0.99, name=hello-world, host=127.0.0.1, addr=127.0.0.1, attr={}, roles={('data', 'd', True), ('ingest', 'i', False), ('remote_cluster_client', 'r', False), ('cluster_manager', 'm', False)}, cluster name=, version=3.0.0.99, features=[], size=185 byte(s) INFO:root:< prefix=b'ES', version=2.10.0.99, type=['request'], message=469 byte(s), id=10, ctx=req={'_system_index_access_allowed': 'false'}, res={}, None, features=[], action=internal:discovery/extensions INFO:root:> prefix=b'ES', version=2.10.0.99, type=['response'], message=167 byte(s), id=6, ctx=req={'_system_index_access_allowed': 'false', 'extension_unique_id': 'hello-world'}, res={}, node=, id=None, features=[], action=internal:discovery/registerrestactions, size=173 byte(s) -``` \ No newline at end of file +``` + +### Call an Extension API + +```bash +curl -XGET "localhost:9200/_extensions/_hello-world/hello" +Hello from Python! 👋 +``` + +OpenSearch invokes an extension endpoint, and you should see some output there. + +``` +INFO:root:< prefix=b'ES', version=2.10.0.99, type=['request'], message=161 byte(s), id=15, ctx=req={'_system_index_access_allowed': 'false'}, res={}, None, features=[], action=internal:extensions/restexecuteonextensiontaction +INFO:root:> prefix=b'ES', version=2.10.0.99, type=['response'], message=108 byte(s), id=15, ctx=req={'_system_index_access_allowed': 'false'}, res={}, , features=[], size=114 byte(s) +``` From 5e300afa357c3eb44fb2c4daba044187821b194f Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 11 Sep 2023 09:31:45 -0400 Subject: [PATCH 2/3] Use @property. Signed-off-by: dblock --- samples/hello/hello.py | 4 ++-- samples/hello/hello_extension.py | 6 ++++-- src/opensearch_sdk_py/api/action_extension.py | 3 ++- src/opensearch_sdk_py/api/extension.py | 3 ++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/samples/hello/hello.py b/samples/hello/hello.py index 262ccec..817618c 100755 --- a/samples/hello/hello.py +++ b/samples/hello/hello.py @@ -98,13 +98,13 @@ async def run_server() -> None: # TODO Move this code to SDK "runner" class # Map interface name to instance interfaces = dict() -for interface in extension.get_implemented_interfaces(): +for interface in extension.implemented_interfaces: interfaces[interface[0]] = interface[1] logging.info(f"Registering {interface[0]} to point to {interface[1]}") # If it's an ActionExtension it has this extension point # TODO This could perhaps be better with isinstance() if "ActionExtension" in interfaces.keys(): - for handler in getattr(interfaces["ActionExtension"], "get_extension_rest_handlers")(): + for handler in getattr(interfaces["ActionExtension"], "extension_rest_handlers"): ExtensionRestHandlers().register(handler) logging.info(f"Registering {handler}") diff --git a/samples/hello/hello_extension.py b/samples/hello/hello_extension.py index 37cb138..034341f 100644 --- a/samples/hello/hello_extension.py +++ b/samples/hello/hello_extension.py @@ -16,7 +16,8 @@ class HelloExtension(Extension, ActionExtension): - def get_implemented_interfaces(self) -> list[tuple]: + @property + def implemented_interfaces(self) -> list[tuple]: # TODO: This is lazy and temporary. # Really we should be using this class to call some SDK class run(), # passing an instance of ourself to the SDK and letting it parse out @@ -24,5 +25,6 @@ def get_implemented_interfaces(self) -> list[tuple]: # implemented functions from the interfaces. return [("Extension", self), ("ActionExtension", self)] - def get_extension_rest_handlers(self) -> list[ExtensionRestHandler]: + @property + def extension_rest_handlers(self) -> list[ExtensionRestHandler]: return [HelloRestHandler()] diff --git a/src/opensearch_sdk_py/api/action_extension.py b/src/opensearch_sdk_py/api/action_extension.py index 2d1e96d..a7f9ba6 100644 --- a/src/opensearch_sdk_py/api/action_extension.py +++ b/src/opensearch_sdk_py/api/action_extension.py @@ -15,8 +15,9 @@ class ActionExtension(ABC): + @property @abstractmethod - def get_extension_rest_handlers(self) -> list[ExtensionRestHandler]: + def extension_rest_handlers(self) -> list[ExtensionRestHandler]: """ Implementer should return a list of classes implementing ExtensionRestHandler """ diff --git a/src/opensearch_sdk_py/api/extension.py b/src/opensearch_sdk_py/api/extension.py index e02f14b..e43043b 100644 --- a/src/opensearch_sdk_py/api/extension.py +++ b/src/opensearch_sdk_py/api/extension.py @@ -13,8 +13,9 @@ class Extension(ABC): + @property @abstractmethod - def get_implemented_interfaces(self) -> list[tuple]: + def implemented_interfaces(self) -> list[tuple]: """ Implementer should return a list of tuples containing the implemented interface (such as Extension, ActionExtension, etc.) and the implementing class. From 7e29371395bbe95dae0201fe305105d2eb3c7ade Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 11 Sep 2023 11:21:37 -0400 Subject: [PATCH 3/3] Made routes and named routes properties. Signed-off-by: dblock --- samples/hello/hello_rest_handler.py | 1 + .../actions/internal/discovery_extensions_request_handler.py | 2 +- src/opensearch_sdk_py/rest/extension_rest_handler.py | 1 + src/opensearch_sdk_py/rest/extension_rest_handlers.py | 3 ++- tests/rest/test_extension_rest_handlers.py | 3 ++- 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/samples/hello/hello_rest_handler.py b/samples/hello/hello_rest_handler.py index b1e6889..d97c30d 100644 --- a/samples/hello/hello_rest_handler.py +++ b/samples/hello/hello_rest_handler.py @@ -25,5 +25,6 @@ def handle_request(self, rest_request: ExtensionRestRequest) -> ExtensionRestRes response_bytes = bytes("Hello from Python!", "utf-8") + b"\x20\xf0\x9f\x91\x8b" return ExtensionRestResponse(RestStatus.OK, response_bytes, ExtensionRestResponse.TEXT_CONTENT_TYPE) + @property def routes(self) -> list[NamedRoute]: return [NamedRoute(method=RestMethod.GET, path="/hello", unique_name="greeting")] diff --git a/src/opensearch_sdk_py/actions/internal/discovery_extensions_request_handler.py b/src/opensearch_sdk_py/actions/internal/discovery_extensions_request_handler.py index 1a8469d..a525b26 100644 --- a/src/opensearch_sdk_py/actions/internal/discovery_extensions_request_handler.py +++ b/src/opensearch_sdk_py/actions/internal/discovery_extensions_request_handler.py @@ -40,7 +40,7 @@ def handle(self, request: OutboundMessageRequest, input: StreamInput) -> StreamO OutboundMessageRequest( thread_context=request.thread_context_struct, features=request.features, - message=RegisterRestActionsRequest("hello-world", ExtensionRestHandlers().named_routes()), + message=RegisterRestActionsRequest("hello-world", ExtensionRestHandlers().named_routes), version=request.version, action="internal:discovery/registerrestactions", is_handshake=False, diff --git a/src/opensearch_sdk_py/rest/extension_rest_handler.py b/src/opensearch_sdk_py/rest/extension_rest_handler.py index b83ebd2..65bd3ca 100644 --- a/src/opensearch_sdk_py/rest/extension_rest_handler.py +++ b/src/opensearch_sdk_py/rest/extension_rest_handler.py @@ -21,5 +21,6 @@ class ExtensionRestHandler(ABC): def handle_request(rest_request: ExtensionRestRequest) -> ExtensionRestResponse: pass + @property def routes(self) -> list[NamedRoute]: return [] diff --git a/src/opensearch_sdk_py/rest/extension_rest_handlers.py b/src/opensearch_sdk_py/rest/extension_rest_handlers.py index 905a5c7..566ee10 100644 --- a/src/opensearch_sdk_py/rest/extension_rest_handlers.py +++ b/src/opensearch_sdk_py/rest/extension_rest_handlers.py @@ -24,12 +24,13 @@ def __new__(cls): # type:ignore return cls._singleton def register(self, klass: ExtensionRestHandler) -> None: - for route in getattr(klass, "routes")(): + for route in klass.routes: # for matching the handler on the extension side only method and path matter self[route.key] = klass # but we have to send the full named route to OpenSearch self._named_routes.append(str(route)) + @property def named_routes(self) -> list[str]: return self._named_routes diff --git a/tests/rest/test_extension_rest_handlers.py b/tests/rest/test_extension_rest_handlers.py index 8b236c4..005f6b1 100644 --- a/tests/rest/test_extension_rest_handlers.py +++ b/tests/rest/test_extension_rest_handlers.py @@ -25,7 +25,7 @@ def test_registers_handler(self) -> None: self.assertEqual(len(ExtensionRestHandlers()), 2) self.assertIsInstance(ExtensionRestHandlers()["GET /foo"], FakeRestHandler) self.assertIsInstance(ExtensionRestHandlers()["GET /bar"], FakeRestHandler) - self.assertListEqual(ExtensionRestHandlers().named_routes(), ["GET /foo get_foo", "GET /bar get_bar"]) + self.assertListEqual(ExtensionRestHandlers().named_routes, ["GET /foo get_foo", "GET /bar get_bar"]) response = handlers.handle("GET /foo", ExtensionRestRequest()) self.assertEqual(response.status, RestStatus.NOT_IMPLEMENTED) @@ -38,5 +38,6 @@ def __init__(self) -> None: def handle_request(self, rest_request: ExtensionRestRequest) -> ExtensionRestResponse: return ExtensionRestResponse(status=RestStatus.NOT_IMPLEMENTED) + @property def routes(self) -> list[NamedRoute]: return [NamedRoute(RestMethod.GET, "/foo", "get_foo"), NamedRoute(RestMethod.GET, "/bar", "get_bar")]