Skip to content

Commit ae25f07

Browse files
Merge pull request #6 from dawagner/sync-api-example
2 parents 8ce9e6a + aaba461 commit ae25f07

3 files changed

Lines changed: 65 additions & 51 deletions

File tree

example/sync-example.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import asyncio
2+
import openspace
3+
4+
# This is an example of how to make synchronous calls to the API
5+
# This is not so useful in scripts but has its use in interactive shells, instead of prepending
6+
# every call with `await`.
7+
8+
ADDRESS = 'localhost'
9+
PORT = 4681
10+
# Create an OpenSpaceApi instance with the OpenSpace address and port
11+
api = openspace.Api(ADDRESS, PORT)
12+
13+
# You might want to handle this differently according to your environment or use-case.
14+
# For instance, this works in a regular python shell or in IPython but `python3 -m asyncio` is
15+
# different.
16+
loop = asyncio.new_event_loop()
17+
def sync_wrapper(f, *args, **kwargs):
18+
return loop.run_until_complete(f(*args, **kwargs))
19+
20+
# warning: all async calls must be performed on the same loop
21+
22+
loop.run_until_complete(api.connect())
23+
sync_os = loop.run_until_complete(api.singleReturnLibrary(sync_wrapper))
24+
25+
# Calls to methods of `sync_os` will block until the call is made and a result is returned:
26+
sync_os.printInfo("foo")
27+
print(sync_os.propertyValue("Scene.Earth.Scale.Scale"))
28+
29+
api.disconnect()

src/openspace/src/api.py

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,25 @@
33
from traceback import print_exc
44
from .topic import Topic
55
from .socketwrapper import SocketWrapper
6+
from functools import partial
67
from typing import Callable, NamedTuple
78
from collections import namedtuple
89

10+
11+
def toNamedTuple(content: dict, name: str = "namedtuple") -> NamedTuple:
12+
""" Recursively converts a `dictionary` to a `namedtuple`. """
13+
14+
T = namedtuple(name, content.keys())
15+
values = []
16+
for k, v in content.items():
17+
if isinstance(v, dict):
18+
values.append(toNamedTuple(v, k))
19+
else:
20+
values.append(v)
21+
22+
return T(*values)
23+
24+
925
class Api:
1026
""" Construct an instance of the OpenSpace API. \n
1127
:param socket - An instance of SocketWrapper.
@@ -322,32 +338,19 @@ async def executeLuaFunction(self, function: str, args, getReturnValue = True):
322338
else:
323339
topic.cancel()
324340

325-
async def library(self, multiReturn: bool) -> NamedTuple:
341+
async def library(self, wrapper: None|Callable = None) -> dict:
326342
""" Get an object representing the OpenSpace lua libarary. \n
327-
:param multiReturn - whether the library should return the raw lua tables.
328-
If this value is true, the 1-indexed lua table will be returned as a dict
329-
If the value is false, then only the first return value will be returned. \n
343+
:param wrapper: if set, wraps all API calls (may be used to make them synchronous)
330344
:return - The lua library, mapped to async python functions. """
331345

332-
def generateAsyncMultiRetFunction(functionName):
333-
async def fun(*args):
334-
try:
335-
return await self.executeLuaFunction(functionName, args)
336-
except Exception as e:
337-
print("Lua exception error: \n", e)
338-
339-
return fun
340-
341-
def generateAsyncSingleRetFunction(functionName):
342-
async def fun(*args):
343-
try:
344-
luaTable = await self.executeLuaFunction(functionName, args)
345-
if luaTable:
346-
return luaTable['1']
347-
return None
348-
except Exception as e:
349-
print("Lua exception error: \n", e)
350-
return fun
346+
async def async_lua_call(functionName, *args):
347+
try:
348+
luaTable = await self.executeLuaFunction(functionName, args)
349+
if luaTable:
350+
return luaTable['1']
351+
return None
352+
except Exception as e:
353+
print("Lua exception error: \n", e)
351354

352355
docs = await self.getDocumentation('lua')
353356

@@ -366,36 +369,18 @@ async def fun(*args):
366369
_lib = '' if subPyLibrary == pyLibrary else libraryName + '.'
367370
fullFunctionName = 'openspace.' + _lib + func['name']
368371

369-
if multiReturn:
370-
subPyLibrary[func['name']] = generateAsyncMultiRetFunction(fullFunctionName)
371-
else:
372-
subPyLibrary[func['name']] = generateAsyncSingleRetFunction(fullFunctionName)
373-
374-
return self.toNamedTuple(pyLibrary, libraryName)
375-
376-
def toNamedTuple(self, content: dict, name: str = "namedtuple") -> NamedTuple:
377-
""" Recursively converts a `dictionary` to a `namedtuple`. """
372+
lua_call = partial(async_lua_call, fullFunctionName)
373+
if wrapper is not None:
374+
lua_call = partial(wrapper, lua_call)
375+
subPyLibrary[func['name']] = lua_call
378376

379-
T = namedtuple(name, content.keys())
380-
values = []
381-
for k, v in content.items():
382-
if isinstance(v, dict):
383-
values.append(self.toNamedTuple(v, k))
384-
else:
385-
values.append(v)
386-
387-
return T(*values)
377+
return toNamedTuple(pyLibrary, libraryName)
388378

389-
async def singleReturnLibrary(self):
379+
async def singleReturnLibrary(self, wrapper=None):
390380
""" Get an object representing the OpenSpace lua library. \n
381+
(deprecated. Use library() instead)
382+
:param wrapper: if set, wraps all API calls (may be used to make them synchronous)
391383
:return - The lua library, mapped to async python functions. This method only
392384
returns the first return value. """
393385

394-
return await self.library(False)
395-
396-
async def multiReturnLibrary(self):
397-
""" Get an object representing the OpenSpace lua library. \n
398-
:return - The lua library, mapped to async python functions. The values returned
399-
by the async functions will be the entire lua tables, with 1-indexed values. """
400-
401-
return await self.library(True)
386+
return await self.library(wrapper)

src/openspace/src/socketwrapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ async def _handle_receive(self):
5656
async def connect(self):
5757
self._client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
5858
self._client.setblocking(False)
59-
self._loop = asyncio.get_event_loop()
59+
self._loop = asyncio.get_running_loop()
6060
try:
6161
await self._loop.sock_connect(self._client, (self._address, self._port))
6262
self._disconnecting = False

0 commit comments

Comments
 (0)