fix: 'Timer' logic

This commit is contained in:
2026-08-11 20:59:21 +08:00
parent 0b359ee4e2
commit bfb45a971e
5 changed files with 59 additions and 59 deletions
+1 -1
View File
@@ -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
View File
@@ -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 }
+23 -23
View File
@@ -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
View File
@@ -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
+7 -7
View File
@@ -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