fix: 'Timer' logic
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
[project]
|
[project]
|
||||||
name = "common"
|
name = "common"
|
||||||
description = "Commonly reusable code"
|
description = "Commonly reusable code"
|
||||||
version = "0.1.19"
|
version = "0.1.20"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="BreakerBear", email="breakerbear@autistic.men" },
|
{ name="BreakerBear", email="breakerbear@autistic.men" },
|
||||||
|
|||||||
+17
-17
@@ -5,12 +5,12 @@ class NotAllowed(Exception): pass
|
|||||||
class Action(Exception, metaclass=ABCMeta):
|
class Action(Exception, metaclass=ABCMeta):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def prepare(cls) -> bool:
|
def prepare(cls) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def perform(cls):
|
def perform(cls):
|
||||||
raise cls()
|
raise cls()
|
||||||
@@ -20,11 +20,11 @@ class ActionFlow:
|
|||||||
self.indices: dict[str, int] = {}
|
self.indices: dict[str, int] = {}
|
||||||
self.actions: list[type[Action]] = []
|
self.actions: list[type[Action]] = []
|
||||||
self.on: list[bool] = []
|
self.on: list[bool] = []
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for key in self.indices.keys():
|
for key in self.indices.keys():
|
||||||
yield (key, self[key])
|
yield (key, self[key])
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
index = self.indices[key]
|
index = self.indices[key]
|
||||||
def inner(*args, **kwargs):
|
def inner(*args, **kwargs):
|
||||||
@@ -32,61 +32,61 @@ class ActionFlow:
|
|||||||
if self.on[index] is None: raise NotAllowed(key)
|
if self.on[index] is None: raise NotAllowed(key)
|
||||||
self.on[index] = self.actions[index].prepare(*args, **kwargs)
|
self.on[index] = self.actions[index].prepare(*args, **kwargs)
|
||||||
return self.on[index]
|
return self.on[index]
|
||||||
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
def queue(self, action: type[Action]) -> bool:
|
def queue(self, action: type[Action]) -> bool:
|
||||||
try: return self[action.__name__]()
|
try: return self[action.__name__]()
|
||||||
except: return False
|
except: return False
|
||||||
|
|
||||||
def react(self, *stage: type[Action]):
|
def react(self, *stage: type[Action]):
|
||||||
for Props in stage:
|
for Props in stage:
|
||||||
self.allow(Props)
|
self.allow(Props)
|
||||||
self.queue(Props)
|
self.queue(Props)
|
||||||
|
|
||||||
while any(self.on):
|
while any(self.on):
|
||||||
for index in self.indices.values():
|
for index in self.indices.values():
|
||||||
if (self.on[index]):
|
if (self.on[index]):
|
||||||
self.on[index] = None
|
self.on[index] = None
|
||||||
self.on[index] = self.actions[index].perform()
|
self.on[index] = self.actions[index].perform()
|
||||||
|
|
||||||
def index(self, action: type[Action]) -> int:
|
def index(self, action: type[Action]) -> int:
|
||||||
name = action.__name__
|
name = action.__name__
|
||||||
index = self.indices.get(name)
|
index = self.indices.get(name)
|
||||||
assert index is not None, "Action '%s' is not registered" % name
|
assert index is not None, "Action '%s' is not registered" % name
|
||||||
return index
|
return index
|
||||||
|
|
||||||
def allow(self, *actions: type[Action]):
|
def allow(self, *actions: type[Action]):
|
||||||
for Item in actions:
|
for Item in actions:
|
||||||
index = self.index(Item)
|
index = self.index(Item)
|
||||||
self.on[index] = False
|
self.on[index] = False
|
||||||
|
|
||||||
def deter(self, *actions: type[Action]):
|
def deter(self, *actions: type[Action]):
|
||||||
for Item in actions:
|
for Item in actions:
|
||||||
index = self.index(Item)
|
index = self.index(Item)
|
||||||
self.on[index] = None
|
self.on[index] = None
|
||||||
|
|
||||||
def stage(self, *preset: type[Action]):
|
def stage(self, *preset: type[Action]):
|
||||||
for Props in preset:
|
for Props in preset:
|
||||||
self.append(Props)
|
self.append(Props)
|
||||||
|
|
||||||
def append(self, action: type[Action]):
|
def append(self, action: type[Action]):
|
||||||
name = action.__name__
|
name = action.__name__
|
||||||
index = len(self.indices)
|
index = len(self.indices)
|
||||||
assert name not in self.indices, "Action '%s' is already registered" % name
|
assert name not in self.indices, "Action '%s' is already registered" % name
|
||||||
|
|
||||||
self.indices[name] = index
|
self.indices[name] = index
|
||||||
self.actions.insert(index, action)
|
self.actions.insert(index, action)
|
||||||
self.on.insert(index, None)
|
self.on.insert(index, None)
|
||||||
|
|
||||||
def remove(self, action: type[Action]):
|
def remove(self, action: type[Action]):
|
||||||
name = action.__name__
|
name = action.__name__
|
||||||
index = self.index(action)
|
index = self.index(action)
|
||||||
|
|
||||||
self.indices.pop(name)
|
self.indices.pop(name)
|
||||||
self.actions.pop(index)
|
self.actions.pop(index)
|
||||||
self.on.pop(index)
|
self.on.pop(index)
|
||||||
|
|
||||||
def capabilities(self) -> dict[str, bool]:
|
def capabilities(self) -> dict[str, bool]:
|
||||||
items = self.indices.items()
|
items = self.indices.items()
|
||||||
return { k: not self.on[v] for k, v in items if self.on[v] is not None }
|
return { k: not self.on[v] for k, v in items if self.on[v] is not None }
|
||||||
|
|||||||
@@ -18,20 +18,20 @@ class Request[T]:
|
|||||||
class ParamsError(Exception): pass
|
class ParamsError(Exception): pass
|
||||||
class NotFound(Exception): pass
|
class NotFound(Exception): pass
|
||||||
class Invalid(Exception): pass
|
class Invalid(Exception): pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, data: str) -> Self:
|
def load(cls, data: str) -> Self:
|
||||||
result = json.loads(data)
|
result = json.loads(data)
|
||||||
return cls(**result)
|
return cls(**result)
|
||||||
|
|
||||||
def handle(self, handlers: dict[str, Any]) -> T:
|
def handle(self, handlers: dict[str, Any]) -> T:
|
||||||
args = self.params
|
args = self.params
|
||||||
handler: Callable[..., T] = handlers.get(self.method)
|
handler: Callable[..., T] = handlers.get(self.method)
|
||||||
|
|
||||||
if handler is None: raise self.NotFound(self.method)
|
if handler is None: raise self.NotFound(self.method)
|
||||||
argcount = handler.__code__.co_argcount
|
argcount = handler.__code__.co_argcount
|
||||||
argnames = handler.__code__.co_varnames[:argcount]
|
argnames = handler.__code__.co_varnames[:argcount]
|
||||||
|
|
||||||
if self.params is None:
|
if self.params is None:
|
||||||
if argcount > 0: raise self.ParamsError('Too less')
|
if argcount > 0: raise self.ParamsError('Too less')
|
||||||
return handler()
|
return handler()
|
||||||
@@ -41,14 +41,14 @@ class Request[T]:
|
|||||||
if isinstance(args, dict):
|
if isinstance(args, dict):
|
||||||
if args.keys() != set(argnames): raise self.ParamsError(args)
|
if args.keys() != set(argnames): raise self.ParamsError(args)
|
||||||
return handler(**args)
|
return handler(**args)
|
||||||
|
|
||||||
raise self.Invalid(type(args))
|
raise self.Invalid(type(args))
|
||||||
|
|
||||||
class Response[T]:
|
class Response[T]:
|
||||||
def __init__(self, id: str|int|None, inner: T):
|
def __init__(self, id: str|int|None, inner: T):
|
||||||
self.id = id
|
self.id = id
|
||||||
self.inner = inner
|
self.inner = inner
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
data = dict()
|
data = dict()
|
||||||
data['id'] = self.id
|
data['id'] = self.id
|
||||||
@@ -62,17 +62,17 @@ class Error:
|
|||||||
def __init__(self, code, data=None):
|
def __init__(self, code, data=None):
|
||||||
self.code: Error.Code = code
|
self.code: Error.Code = code
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
class Code(Enum):
|
class Code(Enum):
|
||||||
PARSE_ERROR = -32700
|
PARSE_ERROR = -32700
|
||||||
INVALID_REQUEST = -32600
|
INVALID_REQUEST = -32600
|
||||||
METHOD_NOT_FOUND = -32601
|
METHOD_NOT_FOUND = -32601
|
||||||
INVALID_PARAMS = -32602
|
INVALID_PARAMS = -32602
|
||||||
INTERNAL_ERROR = -32603
|
INTERNAL_ERROR = -32603
|
||||||
|
|
||||||
def message(self) -> str:
|
def message(self) -> str:
|
||||||
return self.code.name.capitalize().replace('_', ' ')
|
return self.code.name.capitalize().replace('_', ' ')
|
||||||
|
|
||||||
def response(self):
|
def response(self):
|
||||||
result = dict()
|
result = dict()
|
||||||
result['code'] = self.code.value
|
result['code'] = self.code.value
|
||||||
@@ -85,14 +85,14 @@ class History(logging.Handler):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.records = []
|
self.records = []
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
keys = set(['name', 'levelno', 'levelname', 'msg', 'exc_text', 'stack_info', 'created'])
|
keys = set(['name', 'levelno', 'levelname', 'msg', 'exc_text', 'stack_info', 'created'])
|
||||||
values = vars(record)
|
values = vars(record)
|
||||||
values['msg'] %= values['args']
|
values['msg'] %= values['args']
|
||||||
result = { key: values[key] for key in keys }
|
result = { key: values[key] for key in keys }
|
||||||
self.records.append(result)
|
self.records.append(result)
|
||||||
|
|
||||||
def truncate(self) -> list:
|
def truncate(self) -> list:
|
||||||
copy = self.records.copy()
|
copy = self.records.copy()
|
||||||
self.records.clear()
|
self.records.clear()
|
||||||
@@ -108,10 +108,10 @@ class Server(ThreadingHTTPServer):
|
|||||||
class RequestHandler(BaseHTTPRequestHandler):
|
class RequestHandler(BaseHTTPRequestHandler):
|
||||||
server: Server
|
server: Server
|
||||||
protocol_version = 'HTTP/1.1'
|
protocol_version = 'HTTP/1.1'
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
try:
|
try:
|
||||||
match self.headers.get('Sec-Fetch-Dest'):
|
match self.headers.get('Sec-Fetch-Dest'):
|
||||||
@@ -128,16 +128,16 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
response = b'Internal error: %b' % str(e).encode(self.server.encoding)
|
response = b'Internal error: %b' % str(e).encode(self.server.encoding)
|
||||||
self.send_response(500)
|
self.send_response(500)
|
||||||
|
|
||||||
self.send_header('Content-Length', str(len(response)))
|
self.send_header('Content-Length', str(len(response)))
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(response)
|
self.wfile.write(response)
|
||||||
|
|
||||||
def do_HEAD(self):
|
def do_HEAD(self):
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('X-Powered-By', app) if (app := self.server.application) else None
|
self.send_header('X-Powered-By', app) if (app := self.server.application) else None
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def do_POST(self, request=None):
|
def do_POST(self, request=None):
|
||||||
try:
|
try:
|
||||||
size = int(self.headers.get('Content-Length', '0'))
|
size = int(self.headers.get('Content-Length', '0'))
|
||||||
@@ -154,7 +154,7 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
message = Error(Error.Code.METHOD_NOT_FOUND, e)
|
message = Error(Error.Code.METHOD_NOT_FOUND, e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
message = Error(Error.Code.INTERNAL_ERROR, e)
|
message = Error(Error.Code.INTERNAL_ERROR, e)
|
||||||
|
|
||||||
response = Response(request.id if request is not None else None, message)
|
response = Response(request.id if request is not None else None, message)
|
||||||
buffer = bytes(str(response), encoding=self.server.encoding)
|
buffer = bytes(str(response), encoding=self.server.encoding)
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
@@ -168,7 +168,7 @@ class ServiceProvider:
|
|||||||
self.server = Server((self.options['host'], self.options['port']), self.options['handler'])
|
self.server = Server((self.options['host'], self.options['port']), self.options['handler'])
|
||||||
self.server.application = self.options['application']
|
self.server.application = self.options['application']
|
||||||
self.server.encoding = self.options['encoding']
|
self.server.encoding = self.options['encoding']
|
||||||
|
|
||||||
if not self.options['setup']: return
|
if not self.options['setup']: return
|
||||||
import sys, _thread as t
|
import sys, _thread as t
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
@@ -176,7 +176,7 @@ class ServiceProvider:
|
|||||||
logger.addHandler(history)
|
logger.addHandler(history)
|
||||||
self.set('logs', lambda: history.truncate())
|
self.set('logs', lambda: history.truncate())
|
||||||
self.set('exit', lambda: (t.interrupt_main(), sys.exit(0)))
|
self.set('exit', lambda: (t.interrupt_main(), sys.exit(0)))
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Options:
|
class Options:
|
||||||
host : str = '127.0.0.1'
|
host : str = '127.0.0.1'
|
||||||
@@ -186,18 +186,18 @@ class ServiceProvider:
|
|||||||
encoding : str = 'UTF-8'
|
encoding : str = 'UTF-8'
|
||||||
interval : float = 0.2
|
interval : float = 0.2
|
||||||
application : str = ''
|
application : str = ''
|
||||||
|
|
||||||
def add(self, *handlers: Callable[..., Any], pairs: Iterable[tuple[str, Callable[..., Any]]] = None):
|
def add(self, *handlers: Callable[..., Any], pairs: Iterable[tuple[str, Callable[..., Any]]] = None):
|
||||||
for handler in handlers: self.set(handler.__name__, handler)
|
for handler in handlers: self.set(handler.__name__, handler)
|
||||||
for k, v in pairs or tuple(): self.set(k, v)
|
for k, v in pairs or tuple(): self.set(k, v)
|
||||||
|
|
||||||
def set(self, method: str, handler: Callable[..., Any]):
|
def set(self, method: str, handler: Callable[..., Any]):
|
||||||
if method in self.server.handlers: raise KeyError(method)
|
if method in self.server.handlers: raise KeyError(method)
|
||||||
self.server.handlers[method] = handler
|
self.server.handlers[method] = handler
|
||||||
|
|
||||||
def pop(self, method: str) -> Callable[..., Any]:
|
def pop(self, method: str) -> Callable[..., Any]:
|
||||||
return self.server.handlers.pop(method)
|
return self.server.handlers.pop(method)
|
||||||
|
|
||||||
def run(self) -> str:
|
def run(self) -> str:
|
||||||
thread = Thread(target=lambda: self.server.serve_forever(self.options['interval']), daemon=True)
|
thread = Thread(target=lambda: self.server.serve_forever(self.options['interval']), daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|||||||
+11
-11
@@ -4,19 +4,19 @@ from time import time
|
|||||||
class Timer:
|
class Timer:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.checkpoint = None
|
|
||||||
self.accumulator = 0
|
self.accumulator = 0
|
||||||
|
self.checkpoint = 0
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
seconds = floor(time())
|
|
||||||
self.checkpoint = seconds
|
|
||||||
|
|
||||||
def pause(self):
|
|
||||||
assert self.checkpoint is not None, "Uninitialized"
|
|
||||||
self.accumulator = self.delta()
|
self.accumulator = self.delta()
|
||||||
|
self.checkpoint = floor(time())
|
||||||
|
|
||||||
|
def pause(self):
|
||||||
|
self.accumulator = self.delta()
|
||||||
|
self.checkpoint = 0
|
||||||
|
|
||||||
def delta(self):
|
def delta(self):
|
||||||
assert self.checkpoint is not None, "Uninitialized"
|
excess = floor(time()) - rhs if (rhs := self.checkpoint) else 0
|
||||||
return floor(time()) - self.checkpoint + self.accumulator
|
return excess + self.accumulator
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def sleep(seconds: float):
|
|||||||
try: driver.switch_to.alert
|
try: driver.switch_to.alert
|
||||||
except NoAlertPresentException: pass
|
except NoAlertPresentException: pass
|
||||||
except: raise KeyboardInterrupt()
|
except: raise KeyboardInterrupt()
|
||||||
|
|
||||||
try: WebDriverWait(driver, seconds, seconds).until(lambda _: False)
|
try: WebDriverWait(driver, seconds, seconds).until(lambda _: False)
|
||||||
except TimeoutException: pass
|
except TimeoutException: pass
|
||||||
|
|
||||||
@@ -32,15 +32,15 @@ def locate(selector: str, wait=True, condition=True) -> WebElement:
|
|||||||
try:
|
try:
|
||||||
locator = (By.CSS_SELECTOR, selector)
|
locator = (By.CSS_SELECTOR, selector)
|
||||||
if not wait: return driver.find_element(*locator)
|
if not wait: return driver.find_element(*locator)
|
||||||
|
|
||||||
presence = EC.presence_of_element_located(locator)
|
presence = EC.presence_of_element_located(locator)
|
||||||
element = WebDriverWait(driver, parameters.get('timeout', 0)).until(presence, 'Timeout')
|
element = WebDriverWait(driver, parameters.get('timeout', 0)).until(presence, 'Timeout')
|
||||||
driver.execute_script("arguments[0].scrollIntoView({ block: 'center', inline: 'nearest' });", element)
|
driver.execute_script("arguments[0].scrollIntoView({ block: 'center', inline: 'nearest' });", element)
|
||||||
|
|
||||||
if condition is not None and condition != False:
|
if condition is not None and condition != False:
|
||||||
predicate = condition if callable(condition) else EC.visibility_of_element_located
|
predicate = condition if callable(condition) else EC.visibility_of_element_located
|
||||||
element = WebDriverWait(driver, parameters.get('timeout', 0)).until(predicate(locator), 'Timeout')
|
element = WebDriverWait(driver, parameters.get('timeout', 0)).until(predicate(locator), 'Timeout')
|
||||||
|
|
||||||
return element
|
return element
|
||||||
except StaleElementReferenceException:
|
except StaleElementReferenceException:
|
||||||
pass
|
pass
|
||||||
@@ -48,13 +48,13 @@ def locate(selector: str, wait=True, condition=True) -> WebElement:
|
|||||||
def click(selector: str|WebElement, wait=True, condition=False):
|
def click(selector: str|WebElement, wait=True, condition=False):
|
||||||
predicate = None if condition is None else condition or EC.element_to_be_clickable
|
predicate = None if condition is None else condition or EC.element_to_be_clickable
|
||||||
error = False
|
error = False
|
||||||
|
|
||||||
element = locate(selector, wait, predicate) if isinstance(selector, str) else selector
|
element = locate(selector, wait, predicate) if isinstance(selector, str) else selector
|
||||||
counter = lambda: int(element.get_attribute(unique) or '0')
|
counter = lambda: int(element.get_attribute(unique) or '0')
|
||||||
value = counter()
|
value = counter()
|
||||||
driver.execute_script("window.__%s__ = () => { arguments[0].setAttribute('%s', arguments[1] + 1) };" % ((unique,) * 2), element, value)
|
driver.execute_script("window.__%s__ = () => { arguments[0].setAttribute('%s', arguments[1] + 1) };" % ((unique,) * 2), element, value)
|
||||||
driver.execute_script("arguments[0].addEventListener('click', __%s__);" % unique, element)
|
driver.execute_script("arguments[0].addEventListener('click', __%s__);" % unique, element)
|
||||||
|
|
||||||
for _ in range(parameters.get('attempts', 0)):
|
for _ in range(parameters.get('attempts', 0)):
|
||||||
try:
|
try:
|
||||||
if not error: element.click()
|
if not error: element.click()
|
||||||
@@ -69,7 +69,7 @@ def click(selector: str|WebElement, wait=True, condition=False):
|
|||||||
break
|
break
|
||||||
except TimeoutException: continue
|
except TimeoutException: continue
|
||||||
except: break
|
except: break
|
||||||
|
|
||||||
try: driver.execute_script("arguments[0].removeEventListener('click', __%s__);" % unique, element)
|
try: driver.execute_script("arguments[0].removeEventListener('click', __%s__);" % unique, element)
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user