Update.
This commit is contained in:
		
							parent
							
								
									a4bea94495
								
							
						
					
					
						commit
						ba3152f553
					
				
							
								
								
									
										312
									
								
								src/snek/research/serpentarium.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								src/snek/research/serpentarium.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,312 @@ | ||||
| 
 | ||||
| import json | ||||
| import asyncio | ||||
| import aiohttp | ||||
| from aiohttp import web | ||||
| import dataset | ||||
| import dataset.util | ||||
| import traceback | ||||
| import socket | ||||
| import base64 | ||||
| import uuid  | ||||
| 
 | ||||
| class DatasetMethod: | ||||
|     def __init__(self, dt, name): | ||||
|         self.dt = dt | ||||
|         self.name = name  | ||||
| 
 | ||||
|     def __call__(self, *args, **kwargs): | ||||
|         return self.dt.ds.call( | ||||
|             self.dt.name, | ||||
|             self.name, | ||||
|             *args, | ||||
|             **kwargs | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class DatasetTable: | ||||
| 
 | ||||
|     def __init__(self, ds, name): | ||||
|         self.ds = ds  | ||||
|         self.name = name  | ||||
| 
 | ||||
|     def __getattr__(self, name): | ||||
|         return DatasetMethod(self, name) | ||||
| 
 | ||||
| class WebSocketClient: | ||||
|     def __init__(self): | ||||
|         self.buffer = b'' | ||||
|         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||||
|         self.connect()      | ||||
| 
 | ||||
|     def connect(self): | ||||
|         self.socket.connect(("127.0.0.1", 3131)) | ||||
|         key = base64.b64encode(b'1234123412341234').decode('utf-8') | ||||
|         handshake = ( | ||||
|             f"GET /db HTTP/1.1\r\n" | ||||
|             f"Host: localhost:3131\r\n" | ||||
|             f"Upgrade: websocket\r\n" | ||||
|             f"Connection: Upgrade\r\n" | ||||
|             f"Sec-WebSocket-Key: {key}\r\n" | ||||
|             f"Sec-WebSocket-Version: 13\r\n\r\n" | ||||
|         ) | ||||
|         self.socket.sendall(handshake.encode('utf-8')) | ||||
|         response = self.read_until(b'\r\n\r\n') | ||||
|         if b'101 Switching Protocols' not in response: | ||||
|             raise Exception("Failed to connect to WebSocket") | ||||
| 
 | ||||
|     def write(self, message): | ||||
|         message_bytes = message.encode('utf-8') | ||||
|         length = len(message_bytes) | ||||
|         if length <= 125: | ||||
|             self.socket.sendall(b'\x81' + bytes([length]) + message_bytes) | ||||
|         elif length >= 126 and length <= 65535: | ||||
|             self.socket.sendall(b'\x81' + bytes([126]) + length.to_bytes(2, 'big') + message_bytes) | ||||
|         else: | ||||
|             self.socket.sendall(b'\x81' + bytes([127]) + length.to_bytes(8, 'big') + message_bytes) | ||||
|          | ||||
| 
 | ||||
|     def read_until(self, delimiter):  | ||||
|         while True: | ||||
|             find_pos = self.buffer.find(delimiter) | ||||
|             if find_pos != -1: | ||||
|                 data = self.buffer[:find_pos+4] | ||||
|                 self.buffer = self.buffer[find_pos+4:] | ||||
|                 return data  | ||||
|              | ||||
|             chunk = self.socket.recv(1024) | ||||
|             if not chunk: | ||||
|                 return None | ||||
|             self.buffer += chunk | ||||
|              | ||||
|     def read_exactly(self, length): | ||||
|         while len(self.buffer) < length: | ||||
|             chunk = self.socket.recv(length - len(self.buffer)) | ||||
|             if not chunk: | ||||
|                 return None | ||||
|             self.buffer += chunk  | ||||
|         response = self.buffer[: length] | ||||
|         self.buffer = self.buffer[length:] | ||||
|         return response | ||||
| 
 | ||||
|     def read(self): | ||||
|         frame = None  | ||||
|         frame = self.read_exactly(2) | ||||
|         length = frame[1] & 127 | ||||
|         if length == 126: | ||||
|             length = int.from_bytes(self.read_exactly(2), 'big') | ||||
|         elif length == 127: | ||||
|             length = int.from_bytes(self.read_exactly(8), 'big') | ||||
|         message = self.read_exactly(length) | ||||
|         return message | ||||
|      | ||||
|     def close(self): | ||||
|         self.socket.close() | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketClient2: | ||||
|     def __init__(self, uri): | ||||
|         self.uri = uri | ||||
|         self.loop = asyncio.get_event_loop() | ||||
|         self.websocket = None | ||||
|         self.receive_queue = asyncio.Queue() | ||||
| 
 | ||||
|         # Schedule connection setup | ||||
|         if self.loop.is_running(): | ||||
|             # Schedule connect in the existing loop | ||||
|             self._connect_future = asyncio.run_coroutine_threadsafe(self._connect(), self.loop) | ||||
|         else: | ||||
|             # If loop isn't running, connect synchronously | ||||
|             self.loop.run_until_complete(self._connect()) | ||||
| 
 | ||||
|     async def _connect(self): | ||||
|         self.websocket = await websockets.connect(self.uri) | ||||
|         # Start listening for messages | ||||
|         asyncio.create_task(self._receive_loop()) | ||||
| 
 | ||||
|     async def _receive_loop(self): | ||||
|         try: | ||||
|             async for message in self.websocket: | ||||
|                 await self.receive_queue.put(message) | ||||
|         except Exception: | ||||
|             pass  # Handle exceptions as needed | ||||
| 
 | ||||
|     def send(self, message: str): | ||||
|         if self.loop.is_running(): | ||||
|             # Schedule send in the existing loop | ||||
|             asyncio.run_coroutine_threadsafe(self.websocket.send(message), self.loop) | ||||
|         else: | ||||
|             # If loop isn't running, run directly | ||||
|             self.loop.run_until_complete(self.websocket.send(message)) | ||||
| 
 | ||||
|     def receive(self): | ||||
|         # Wait for a message synchronously | ||||
|         future = asyncio.run_coroutine_threadsafe(self.receive_queue.get(), self.loop) | ||||
|         return future.result() | ||||
| 
 | ||||
|     def close(self): | ||||
|         if self.websocket: | ||||
|             if self.loop.is_running(): | ||||
|                 asyncio.run_coroutine_threadsafe(self.websocket.close(), self.loop) | ||||
|             else: | ||||
|                 self.loop.run_until_complete(self.websocket.close()) | ||||
| 
 | ||||
| 
 | ||||
| import websockets  | ||||
| 
 | ||||
| class DatasetWrapper(object): | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.ws = WebSocketClient()  | ||||
| 
 | ||||
|     def begin(self): | ||||
|         self.call(None, 'begin') | ||||
| 
 | ||||
|     def commit(self): | ||||
|         self.call(None, 'commit') | ||||
| 
 | ||||
|     def __getitem__(self, name): | ||||
|         return DatasetTable(self, name) | ||||
| 
 | ||||
|     def query(self, *args, **kwargs): | ||||
|         return self.call(None, 'query', *args, **kwargs) | ||||
| 
 | ||||
|     def call(self, table, method, *args, **kwargs): | ||||
|         payload = {"table": table, "method": method, "args": args, "kwargs": kwargs,"call_uid":None} | ||||
|         #if method in ['find','find_one']: | ||||
|         payload["call_uid"] = str(uuid.uuid4())     | ||||
|         self.ws.write(json.dumps(payload)) | ||||
|         if payload["call_uid"]: | ||||
|             response = self.ws.read() | ||||
|             return json.loads(response)['result'] | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class DatasetWebSocketView: | ||||
|     def __init__(self): | ||||
|         self.ws = None | ||||
|         self.db = dataset.connect('sqlite:///snek.db') | ||||
|         self.setattr(self, "db", self.get) | ||||
|         self.setattr(self, "db", self.set) | ||||
|         ) | ||||
|         super() | ||||
|      | ||||
|     def format_result(self, result): | ||||
|          | ||||
|         try: | ||||
|             return dict(result) | ||||
|         except: | ||||
|             pass | ||||
|         try: | ||||
|             return [dict(row) for row in result] | ||||
|         except: | ||||
|             pass | ||||
|         return result | ||||
| 
 | ||||
|     async def send_str(self, msg): | ||||
|         return await self.ws.send_str(msg) | ||||
| 
 | ||||
|     def get(self, key): | ||||
|         returnl loads(dict(self.db['_kv'].get(key=key)['value'])) | ||||
| 
 | ||||
|     def set(self, key, value): | ||||
|         return self.db['_kv'].upsert({'key': key, 'value': json.dumps(value)}, ['key']) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     async def handle(self, request): | ||||
|         ws = web.WebSocketResponse() | ||||
|         await ws.prepare(request) | ||||
|         self.ws = ws | ||||
| 
 | ||||
|         async for msg in ws: | ||||
|             if msg.type == aiohttp.WSMsgType.TEXT: | ||||
|                 try: | ||||
|                     data = json.loads(msg.data) | ||||
|                     call_uid = data.get("call_uid") | ||||
|                     method = data.get("method") | ||||
|                     table_name = data.get("table") | ||||
|                     args = data.get("args", {}) | ||||
|                     kwargs = data.get("kwargs", {}) | ||||
|                      | ||||
| 
 | ||||
|                     function = getattr(self.db, method, None) | ||||
|                     if table_name: | ||||
|                         function = getattr(self.db[table_name], method, None) | ||||
|                      | ||||
|                     print(method, table_name, args, kwargs,flush=True) | ||||
|                      | ||||
|                     if function: | ||||
|                         response = {} | ||||
|                         try: | ||||
|                             result = function(*args, **kwargs) | ||||
|                             print(result)  | ||||
|                             response['result'] = self.format_result(result) | ||||
|                             response["call_uid"] = call_uid | ||||
|                             response["success"] = True | ||||
|                         except Exception as e: | ||||
|                             response["call_uid"] = call_uid | ||||
|                             response["success"] = False | ||||
|                             response["error"] = str(e) | ||||
|                             response["traceback"] = traceback.format_exc() | ||||
|                          | ||||
|                         if call_uid: | ||||
|                             await self.send_str(json.dumps(response,default=str)) | ||||
|                     else: | ||||
|                         await self.send_str(json.dumps({"status": "error", "error":"Method not found.","call_uid": call_uid})) | ||||
|                 except Exception as e: | ||||
|                     await self.send_str(json.dumps({"success": False,"call_uid": call_uid, "error": str(e), "error": str(e), "traceback": traceback.format_exc()},default=str)) | ||||
|             elif msg.type == aiohttp.WSMsgType.ERROR: | ||||
|                 print('ws connection closed with exception %s' % ws.exception()) | ||||
| 
 | ||||
|         return ws | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
| app = web.Application() | ||||
| view = DatasetWebSocketView() | ||||
| app.router.add_get('/db', view.handle) | ||||
| 
 | ||||
| async def run_server(): | ||||
| 
 | ||||
| 
 | ||||
|     runner = web.AppRunner(app) | ||||
|     await runner.setup() | ||||
|     site = web.TCPSite(runner, 'localhost', 3131) | ||||
|     await site.start() | ||||
| 
 | ||||
|     print("Server started at http://localhost:8080") | ||||
|     await asyncio.Event().wait() | ||||
| 
 | ||||
| async def client(): | ||||
|     print("x") | ||||
|     d = DatasetWrapper() | ||||
|     print("y") | ||||
|      | ||||
|     for x in range(100): | ||||
|         for x in range(100): | ||||
|             if d['test'].insert({"name": "test", "number":x}): | ||||
|                 print(".",end="",flush=True) | ||||
|         print("")     | ||||
|         print(d['test'].find_one(name="test", order_by="-number"))  | ||||
| 
 | ||||
|     print("DONE") | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| import time  | ||||
| async def main(): | ||||
|     await run_server() | ||||
| 
 | ||||
| import sys  | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     if sys.argv[1] == 'server': | ||||
|         asyncio.run(main()) | ||||
|     if sys.argv[1] == 'client': | ||||
|         asyncio.run(client()) | ||||
							
								
								
									
										51
									
								
								src/snek/research/serptest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/snek/research/serptest.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| import snek.serpentarium | ||||
| 
 | ||||
| import time  | ||||
| 
 | ||||
| from concurrent.futures import ProcessPoolExecutor | ||||
| 
 | ||||
| durations = [] | ||||
| 
 | ||||
| def task1(): | ||||
|     global durations  | ||||
|     client = snek.serpentarium.DatasetWrapper() | ||||
| 
 | ||||
|     start=time.time() | ||||
|     for x in range(1500): | ||||
|      | ||||
|         client['a'].delete() | ||||
|         client['a'].insert({"foo": x}) | ||||
|         client['a'].find(foo=x)  | ||||
|         client['a'].find_one(foo=x) | ||||
|         client['a'].count() | ||||
|         #print(client['a'].find(foo=x) ) | ||||
|         #print(client['a'].find_one(foo=x) ) | ||||
|         #print(client['a'].count()) | ||||
|     client.close() | ||||
|     duration1 = f"{time.time()-start}" | ||||
|     durations.append(duration1) | ||||
|     print(durations) | ||||
| 
 | ||||
| with ProcessPoolExecutor(max_workers=4) as executor: | ||||
|     tasks = [executor.submit(task1), | ||||
|         executor.submit(task1), | ||||
|         executor.submit(task1), | ||||
|         executor.submit(task1) | ||||
|     ] | ||||
|     for task in tasks: | ||||
|         task.result() | ||||
| 
 | ||||
| 
 | ||||
| import dataset  | ||||
| client = dataset.connect("sqlite:///snek.db") | ||||
| start=time.time() | ||||
| for x in range(1500): | ||||
| 
 | ||||
|     client['a'].delete() | ||||
|     client['a'].insert({"foo": x}) | ||||
|     print([dict(row) for row in client['a'].find(foo=x)]) | ||||
|     print(dict(client['a'].find_one(foo=x) )) | ||||
|     print(client['a'].count()) | ||||
| duration2 = f"{time.time()-start}" | ||||
| 
 | ||||
| print(duration1,duration2) | ||||
							
								
								
									
										103
									
								
								src/snek/schema.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/snek/schema.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| CREATE TABLE IF NOT EXISTS http_access ( | ||||
| 	id INTEGER NOT NULL,  | ||||
| 	created TEXT,  | ||||
| 	path TEXT,  | ||||
| 	duration FLOAT,  | ||||
| 	PRIMARY KEY (id) | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS user ( | ||||
| 	id INTEGER NOT NULL,  | ||||
| 	color TEXT,  | ||||
| 	created_at TEXT,  | ||||
| 	deleted_at TEXT,  | ||||
| 	email TEXT,  | ||||
| 	is_admin TEXT,  | ||||
| 	last_ping TEXT,  | ||||
| 	nick TEXT,  | ||||
| 	password TEXT,  | ||||
| 	uid TEXT,  | ||||
| 	updated_at TEXT,  | ||||
| 	username TEXT,  | ||||
| 	PRIMARY KEY (id) | ||||
| ); | ||||
| CREATE INDEX IF NOT EXISTS ix_user_e2577dd78b54fe28 ON user (uid); | ||||
| CREATE TABLE IF NOT EXISTS channel ( | ||||
| 	id INTEGER NOT NULL,  | ||||
| 	created_at TEXT,  | ||||
| 	created_by_uid TEXT,  | ||||
| 	deleted_at TEXT,  | ||||
| 	description TEXT,  | ||||
| 	"index" BIGINT,  | ||||
| 	is_listed BOOLEAN,  | ||||
| 	is_private BOOLEAN,  | ||||
| 	label TEXT,  | ||||
| 	last_message_on TEXT,  | ||||
| 	tag TEXT,  | ||||
| 	uid TEXT,  | ||||
| 	updated_at TEXT,  | ||||
| 	PRIMARY KEY (id) | ||||
| ); | ||||
| CREATE INDEX IF NOT EXISTS ix_channel_e2577dd78b54fe28 ON channel (uid); | ||||
| CREATE TABLE IF NOT EXISTS channel_member ( | ||||
| 	id INTEGER NOT NULL,  | ||||
| 	channel_uid TEXT,  | ||||
| 	created_at TEXT,  | ||||
| 	deleted_at TEXT,  | ||||
| 	is_banned BOOLEAN,  | ||||
| 	is_moderator BOOLEAN,  | ||||
| 	is_muted BOOLEAN,  | ||||
| 	is_read_only BOOLEAN,  | ||||
| 	label TEXT,  | ||||
| 	new_count BIGINT,  | ||||
| 	uid TEXT,  | ||||
| 	updated_at TEXT,  | ||||
| 	user_uid TEXT,  | ||||
| 	PRIMARY KEY (id) | ||||
| ); | ||||
| CREATE INDEX IF NOT EXISTS ix_channel_member_e2577dd78b54fe28 ON channel_member (uid); | ||||
| CREATE TABLE IF NOT EXISTS broadcast ( | ||||
| 	id INTEGER NOT NULL,  | ||||
| 	channel_uid TEXT,  | ||||
| 	message TEXT,  | ||||
| 	created_at TEXT,  | ||||
| 	PRIMARY KEY (id) | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS channel_message ( | ||||
| 	id INTEGER NOT NULL,  | ||||
| 	channel_uid TEXT,  | ||||
| 	created_at TEXT,  | ||||
| 	deleted_at TEXT,  | ||||
| 	html TEXT,  | ||||
| 	message TEXT,  | ||||
| 	uid TEXT,  | ||||
| 	updated_at TEXT,  | ||||
| 	user_uid TEXT,  | ||||
| 	PRIMARY KEY (id) | ||||
| ); | ||||
| CREATE INDEX IF NOT EXISTS ix_channel_message_e2577dd78b54fe28 ON channel_message (uid); | ||||
| CREATE TABLE IF NOT EXISTS notification ( | ||||
| 	id INTEGER NOT NULL,  | ||||
| 	created_at TEXT,  | ||||
| 	deleted_at TEXT,  | ||||
| 	message TEXT,  | ||||
| 	object_type TEXT,  | ||||
| 	object_uid TEXT,  | ||||
| 	read_at TEXT,  | ||||
| 	uid TEXT,  | ||||
| 	updated_at TEXT,  | ||||
| 	user_uid TEXT,  | ||||
| 	PRIMARY KEY (id) | ||||
| ); | ||||
| CREATE INDEX IF NOT EXISTS ix_notification_e2577dd78b54fe28 ON notification (uid); | ||||
| CREATE TABLE IF NOT EXISTS repository ( | ||||
| 	id INTEGER NOT NULL,  | ||||
| 	created_at TEXT,  | ||||
| 	deleted_at TEXT,  | ||||
| 	is_private BIGINT,  | ||||
| 	name TEXT,  | ||||
| 	uid TEXT,  | ||||
| 	updated_at TEXT,  | ||||
| 	user_uid TEXT,  | ||||
| 	PRIMARY KEY (id) | ||||
| ); | ||||
| CREATE INDEX IF NOT EXISTS ix_repository_e2577dd78b54fe28 ON repository (uid); | ||||
							
								
								
									
										116
									
								
								src/snek/snode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/snek/snode.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | ||||
| import aiohttp | ||||
| 
 | ||||
| ENABLED = False | ||||
| 
 | ||||
| import aiohttp | ||||
| import asyncio | ||||
| from aiohttp import web | ||||
| 
 | ||||
| import sqlite3 | ||||
| 
 | ||||
| import dataset | ||||
| from sqlalchemy import event | ||||
| from sqlalchemy.engine import Engine | ||||
| 
 | ||||
| import json  | ||||
| 
 | ||||
| queue = asyncio.Queue() | ||||
| 
 | ||||
| class State: | ||||
|     do_not_sync = False  | ||||
| 
 | ||||
| async def sync_service(app): | ||||
|     if not ENABLED: | ||||
|         return   | ||||
|     session = aiohttp.ClientSession() | ||||
|     async with session.ws_connect('http://localhost:3131/ws') as ws: | ||||
|         async def receive(): | ||||
| 
 | ||||
|             queries_synced = 0 | ||||
|             async for msg in ws: | ||||
|                 if msg.type == aiohttp.WSMsgType.TEXT: | ||||
|                     try: | ||||
|                         data = json.loads(msg.data) | ||||
|                         State.do_not_sync = True  | ||||
|                         app.db.execute(*data) | ||||
|                         app.db.commit() | ||||
|                         State.do_not_sync = False | ||||
|                         queries_synced += 1 | ||||
|                         print("queries synced: " + str(queries_synced)) | ||||
|                         print(*data) | ||||
|                         await app.services.socket.broadcast_event() | ||||
|                     except Exception as e: | ||||
|                         print(e) | ||||
|                     pass  | ||||
|                     #print(f"Received: {msg.data}") | ||||
|                 elif msg.type == aiohttp.WSMsgType.ERROR: | ||||
|                     break | ||||
|         async def write(): | ||||
|             while True: | ||||
|                 msg = await queue.get() | ||||
|                 await ws.send_str(json.dumps(msg,default=str)) | ||||
|                 queue.task_done() | ||||
| 
 | ||||
|         await asyncio.gather(receive(), write()) | ||||
| 
 | ||||
|     await session.close() | ||||
| 
 | ||||
| queries_queued = 0 | ||||
| # Attach a listener to log all executed statements | ||||
| @event.listens_for(Engine, "before_cursor_execute") | ||||
| def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): | ||||
|     if not ENABLED: | ||||
|         return | ||||
|     global queries_queued | ||||
|     if State.do_not_sync: | ||||
|         print(statement,parameters) | ||||
|         return | ||||
|     if statement.startswith("SELECT"): | ||||
|         return | ||||
|     queue.put_nowait((statement, parameters)) | ||||
|     queries_queued += 1 | ||||
|     print("Queries queued: " + str(queries_queued)) | ||||
| 
 | ||||
| async def websocket_handler(request): | ||||
|     queries_broadcasted = 0  | ||||
|     ws = web.WebSocketResponse() | ||||
|     await ws.prepare(request) | ||||
|     request.app['websockets'].append(ws) | ||||
|     async for msg in ws: | ||||
|         if msg.type == aiohttp.WSMsgType.TEXT: | ||||
|             for client in request.app['websockets']: | ||||
|                 if client != ws: | ||||
|                     await client.send_str(msg.data) | ||||
|             cursor = request.app['db'].cursor() | ||||
|             data = json.loads(msg.data) | ||||
|             queries_broadcasted += 1 | ||||
| 
 | ||||
|             cursor.execute(*data) | ||||
|             cursor.close() | ||||
|             print("Queries broadcasted: " + str(queries_broadcasted)) | ||||
|         elif msg.type == aiohttp.WSMsgType.ERROR: | ||||
|             print(f'WebSocket connection closed with exception {ws.exception()}') | ||||
| 
 | ||||
|     request.app['websockets'].remove(ws) | ||||
|     return ws | ||||
| 
 | ||||
| app = web.Application() | ||||
| app['websockets'] = [] | ||||
| 
 | ||||
| app.router.add_get('/ws', websocket_handler) | ||||
| 
 | ||||
| async def on_startup(app): | ||||
|     app['db'] = sqlite3.connect('snek.db') | ||||
|     print("Server starting...") | ||||
| 
 | ||||
| async def on_cleanup(app): | ||||
|     for ws in app['websockets']: | ||||
|         await ws.close() | ||||
|     app['db'].close() | ||||
| 
 | ||||
| app.on_startup.append(on_startup) | ||||
| app.on_cleanup.append(on_cleanup) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     web.run_app(app, host='127.0.0.1', port=3131) | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user