Skip to content

Commit

Permalink
Use properties. (#29)
Browse files Browse the repository at this point in the history
* Added a README on invoking the REST endpoint.

Signed-off-by: dblock <dblock@amazon.com>

* Use @Property.

Signed-off-by: dblock <dblock@amazon.com>

* Made routes and named routes properties.

Signed-off-by: dblock <dblock@amazon.com>

---------

Signed-off-by: dblock <dblock@amazon.com>
  • Loading branch information
dblock committed Sep 11, 2023
1 parent d649774 commit 95ddfb1
Show file tree
Hide file tree
Showing 10 changed files with 40 additions and 10 deletions.
24 changes: 23 additions & 1 deletion samples/hello/README.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down Expand Up @@ -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)
```
```

### 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={}, <opensearch_sdk_py.rest.rest_execute_on_extension_response.RestExecuteOnExtensionResponse object at 0x1048cb450>, features=[], size=114 byte(s)
```
4 changes: 2 additions & 2 deletions samples/hello/hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

Expand Down
6 changes: 4 additions & 2 deletions samples/hello/hello_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@


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
# the superclass names with class.__mro__ and calling the appropriate
# 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()]
1 change: 1 addition & 0 deletions samples/hello/hello_rest_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/opensearch_sdk_py/api/action_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
3 changes: 2 additions & 1 deletion src/opensearch_sdk_py/api/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/opensearch_sdk_py/rest/extension_rest_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ class ExtensionRestHandler(ABC):
def handle_request(rest_request: ExtensionRestRequest) -> ExtensionRestResponse:
pass

@property
def routes(self) -> list[NamedRoute]:
return []
3 changes: 2 additions & 1 deletion src/opensearch_sdk_py/rest/extension_rest_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion tests/rest/test_extension_rest_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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")]

0 comments on commit 95ddfb1

Please sign in to comment.