BotZ/bot_z/async_operator.py

103 lines
3.3 KiB
Python
Raw Normal View History

2019-07-30 22:50:40 +02:00
# -*- encoding: utf-8 -*-
"""
AsyncOperator is an async wrapper around the Operator sync operations.
It puts in the loop background the execution of the same methods in
the Operator.
"""
import asyncio
2019-08-18 03:22:14 +02:00
from concurrent.futures import ThreadPoolExecutor, Executor
2019-07-30 22:50:40 +02:00
import functools
import logging
2019-08-01 16:12:55 +02:00
import typing as T
2019-07-30 22:50:40 +02:00
from bot_z.operator import Operator
2019-08-01 00:28:09 +02:00
from bot_z.exceptions import OperationFailed
2019-07-30 22:50:40 +02:00
alog = logging.getLogger("asyncio")
2019-08-18 03:22:14 +02:00
async def push_to_loop(
2019-08-01 16:12:55 +02:00
loop: asyncio.AbstractEventLoop,
2019-08-18 03:22:14 +02:00
executor: Executor,
2019-08-01 16:12:55 +02:00
func: T.Callable,
*args,
**kwargs
) -> T.Any:
2019-07-30 22:50:40 +02:00
sync_task = [
loop.run_in_executor(executor, functools.partial(func, **kwargs), *args)
]
res_set, _ = await asyncio.wait(sync_task, loop=loop)
res = res_set.pop()
return res.result()
2019-08-01 00:28:09 +02:00
# TODO: make it JSON-serializable
2019-07-30 22:50:40 +02:00
class AsyncOperator(object):
"""
This is the async version of the Operator.
This class DOES NOT inherit from Operator: it contains an
active instance of it.
"""
def __init__(self, base_uri: str, name: str, *args, **kwargs) -> None:
self.name = name
self.base_uri = base_uri
self.op = Operator(base_uri, name, *args, **kwargs)
self.executor = ThreadPoolExecutor(max_workers=2)
self.loop = asyncio.get_event_loop()
async def login(self, username: str, password: str) -> None:
"""Perform the login. Raise if failing."""
alog.debug("Logging in [%s]", self.name)
2019-08-18 03:22:14 +02:00
_ = await push_to_loop(
2019-07-30 22:50:40 +02:00
self.loop, self.executor, self.op.login, username, password
)
if not self.op.logged_in:
2019-08-01 00:28:09 +02:00
raise OperationFailed("Failed to login.")
2019-07-30 22:50:40 +02:00
alog.info("Logged in [%s]", self.name)
async def logout(self) -> None:
"""Perform the logout. Raise if failing."""
alog.debug("Logging out [%s]", self.name)
2019-08-18 03:22:14 +02:00
_ = await push_to_loop(self.loop, self.executor, self.op.logout)
2019-07-30 22:50:40 +02:00
if self.op.logged_in:
2019-08-01 00:28:09 +02:00
raise OperationFailed("Failed to logout.")
2019-07-30 22:50:40 +02:00
alog.info("Logged out [%s]", self.name)
async def checkin(self) -> None:
"""Perform the checkin. Raise if failing."""
alog.debug("Checking in [%s]", self.name)
2019-08-18 03:22:14 +02:00
_ = await push_to_loop(self.loop, self.executor, self.op.check_in)
2019-07-30 22:50:40 +02:00
if not self.op.checked_in:
2019-08-01 00:28:09 +02:00
raise OperationFailed("Failed to checkin.")
2019-07-30 22:50:40 +02:00
alog.info("Checked in [%s]", self.name)
async def checkout(self) -> None:
"""Perform the checkout. Raise if failing."""
alog.debug("Checking out [%s]", self.name)
2019-08-18 03:22:14 +02:00
_ = await push_to_loop(self.loop, self.executor, self.op.check_out)
2019-07-30 22:50:40 +02:00
if self.op.checked_in:
2019-08-01 00:28:09 +02:00
raise OperationFailed("Failed to checkout.")
2019-07-30 22:50:40 +02:00
alog.info("Checked out [%s]", self.name)
2019-08-01 16:17:14 +02:00
async def get_movements(self) -> T.List[T.Optional[T.Tuple[T.Text, T.Text]]]:
"""
Retrieves the list of movements as a list of tuples.
The list may be empty.
"""
alog.debug("Retrieving the list of movements [%s]", self.name)
2019-08-18 03:22:14 +02:00
res = await push_to_loop(self.loop, self.executor, self.op.get_movements)
2019-08-01 16:17:14 +02:00
alog.info("List of movements [%s]: %s", self.name, res)
return res
2019-07-30 22:50:40 +02:00
@property
def logged_in(self) -> bool:
return self.op.logged_in
@property
def checked_in(self) -> bool:
return self.op.checked_in