diff --git a/.gitignore b/.gitignore index 5b4d23f..66427ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ yaboli/__pycache__/ +*.db diff --git a/DBTest.py b/DBTest.py new file mode 100644 index 0000000..5322432 --- /dev/null +++ b/DBTest.py @@ -0,0 +1,25 @@ +import asyncio +import yaboli + + + +class ExampleDB(yaboli.Database): + @yaboli.Database.operation + def sample_operation(connection, *args): + print(args) + #return a + b + print("returning 15...") + return 15 + +async def run(): + db = ExampleDB("test.db") + print(db.sample_operation) + #print(db.sample_operation(1, 2)) + result = await db.sample_operation(1, 2) + print(result) + +def main(): + asyncio.get_event_loop().run_until_complete(run()) + +if __name__ == "__main__": + main() diff --git a/TemplateBot.py b/TemplateBot.py new file mode 100644 index 0000000..a4615c8 --- /dev/null +++ b/TemplateBot.py @@ -0,0 +1,92 @@ +""" +Copy this template script and modify it to create a new bot. +""" + +import yaboli +from yaboli.utils import * +import sys + + + +class YourBot(yaboli.Bot): + """ + Your bot's docstring + """ + + def __init__(self): + super().__init__("Your bot's name") + + # set help and other settings here + #self.help_general = None + #self.help_specific = "No help available" + #self.killable = True + #self.kill_message = "/me *poof*" + #self.restartable = True + #self.restart_message = "/me temporary *poof*" + + # Event callbacks - just fill in your code. + # If the function contains a super(), DON'T remove it unless you know what you're doing! + # (You can remove the function itself though.) + # When you're done, remove all unneeded functions. + + async def on_connected(self): + await super().on_connected() + + async def on_disconnected(self): + await super().on_disconnected() + + async def on_bounce(self, reason=None, auth_options=[], agent_id=None, ip=None): + await super().on_bounce(reason, auth_options, agent_id, ip) + + async def on_disconnect(self, reason): + pass + + async def on_hello(self, user_id, session, room_is_private, version, account=None, + account_has_access=None, account_email_verified=None): + pass + + async def on_join(self, session): + pass + + async def on_login(self, account_id): + pass + + async def on_logout(self): + pass + + async def on_network(self, ntype, server_id, server_era): + pass + + async def on_nick(self, session_id, user_id, from_nick, to_nick): + pass + + async def on_edit_message(self, edit_id, message): + pass + + async def on_part(self, session): + pass + + async def on_ping(self, ptime, pnext): + await super().on_ping(ptime, pnext) + + async def on_pm_initiate(self, from_id, from_nick, from_room, pm_id): + pass + + async def on_send(self, message): + await super().on_send(message) # This is where yaboli.bot reacts to commands + + async def on_snapshot(self, user_id, session_id, version, listing, log, nick=None, + pm_with_nick=None, pm_with_user_id=None): + await super().on_snapshot(user_id, session_id, version, listing, log, nick, pm_with_nick, + pm_with_user_id) + +def main(): + if len(sys.argv) == 2: + run_bot(YourBot, sys.argv[1]) + else: + print("USAGE:") + print(f" {sys.argv[0]} ") + return + +if __name__ == "__main__": + main() diff --git a/yaboli/__init__.py b/yaboli/__init__.py index d46b6ca..3d36e7d 100644 --- a/yaboli/__init__.py +++ b/yaboli/__init__.py @@ -10,6 +10,7 @@ logger.setLevel(logging.INFO) from .bot import * from .connection import * from .controller import * +from .database import * from .room import * from .utils import * diff --git a/yaboli/database.py b/yaboli/database.py new file mode 100644 index 0000000..c14863d --- /dev/null +++ b/yaboli/database.py @@ -0,0 +1,88 @@ +import asyncio +from functools import wraps +import sqlite3 +import threading + +__all__ = ["Database"] + + + +def shielded(afunc): + #@wraps(afunc) + async def wrapper(*args, **kwargs): + return await asyncio.shield(afunc(*args, **kwargs)) + return wrapper + +class PooledConnection: + def __init__(self, pool): + self._pool = pool + + self.connection = None + + async def open(self): + self.connection = await self._pool._request() + print(self.connection) + + async def close(self): + conn = self.connection + self.connection = None + await self._pool._return(conn) + + async def __aenter__(self): + await self.open() + return self + + async def __aexit__(self, exc_type, exc, tb): + await self.close() + +class Pool: + def __init__(self, filename, size=10): + self.filename = filename + self.size = size + + self._available_connections = asyncio.Queue() + + for i in range(size): + conn = sqlite3.connect(self.filename, check_same_thread=False) + self._available_connections.put_nowait(conn) + + def connection(self): + return PooledConnection(self) + + async def _request(self): + return await self._available_connections.get() + + async def _return(self, conn): + await self._available_connections.put(conn) + +class Database: + def __init__(self, filename, pool_size=10, event_loop=None): + self._filename = filename + self._pool = Pool(filename, size=pool_size) + self._loop = event_loop or asyncio.get_event_loop() + + def operation(func): + @wraps(func) + @shielded + async def wrapper(self, *args, **kwargs): + async with self._pool.connection() as conn: + return await self._run_in_thread(func, conn.connection, *args, **kwargs) + return wrapper + + @staticmethod + def _target_function(loop, future, func, *args, **kwargs): + result = None + try: + result = func(*args, **kwargs) + finally: + loop.call_soon_threadsafe(future.set_result, result) + + async def _run_in_thread(self, func, *args, **kwargs): + finished = asyncio.Future() + target_args = (self._loop, finished, func, *args) + + thread = threading.Thread(target=self._target_function, args=target_args, kwargs=kwargs) + thread.start() + + await finished + return finished.result()