wwwpy
Advanced tools
| from __future__ import annotations | ||
| import asyncio | ||
| import logging | ||
| from typing import Callable, Optional, Dict, Union, Awaitable | ||
| import js | ||
| from js import console, window, EventTarget, KeyboardEvent | ||
| from pyodide.ffi import create_proxy, to_js | ||
| logger = logging.getLogger(__name__) | ||
| HotkeyHandler = Union[Callable[['KeyboardEvent'], Optional[bool]], Callable[['KeyboardEvent'], Awaitable[None]]] | ||
| class Hotkey: | ||
| def __init__(self, element: EventTarget): | ||
| self.element = element | ||
| self.handlers: Dict[str, HotkeyHandler] = dict() | ||
| self.debug = False | ||
| self._proxy = create_proxy(self._detect_hotkey) | ||
| self.install() | ||
| def install(self): | ||
| self.element.addEventListener('keydown', self._proxy, False) | ||
| def uninstall(self): | ||
| self.element.removeEventListener('keydown', self._proxy, False) | ||
| # todo this should be called 'set' | ||
| def add(self, hotkey: str, handler: HotkeyHandler) -> 'Hotkey': | ||
| """ | ||
| Registers a hotkey with its corresponding handler. | ||
| Adds a key-handler pair to the handlers dictionary, associating the hotkey with | ||
| the given handler function or callable. | ||
| Parameters | ||
| ---------- | ||
| hotkey : str | ||
| The key combination that will trigger the handler. Examples: 'CTRL-S', 'Escape', 'META-Backspace' | ||
| , 'CTRL-SHIFT-ALT-META-F1', this last example serves to know the order of the modifiers. | ||
| handler : HotkeyHandler | ||
| A function or callable that will be called when the hotkey is pressed. | ||
| """ | ||
| self.handlers[hotkey] = handler | ||
| return self | ||
| @classmethod | ||
| def keyboard_event(cls, e): | ||
| return js.eval('(e) => e instanceof KeyboardEvent ')(e) | ||
| def _detect_hotkey(self, e): | ||
| if not self.keyboard_event(e): | ||
| return | ||
| key = '' | ||
| if e.ctrlKey: key += 'CTRL-' | ||
| if e.shiftKey: key += 'SHIFT-' | ||
| if e.altKey: key += 'ALT-' | ||
| if e.metaKey: key += 'META-' | ||
| upc: str = e.key | ||
| if len(upc) == 1: upc = upc.upper() | ||
| key += upc | ||
| if self.debug: | ||
| console.log(key, to_js(e)) | ||
| logger.debug(f'key = `{key}`') | ||
| handle = self.handlers.get(key, None) | ||
| if handle is None: | ||
| return | ||
| e.hotkey = key # add the hotkey attribute to the event | ||
| if asyncio.iscoroutinefunction(handle): | ||
| e.preventDefault() | ||
| e.stopPropagation() | ||
| asyncio.create_task(handle(e)) | ||
| return | ||
| res = handle(e) | ||
| if not res: | ||
| return | ||
| console.log(f'prevent default for {key}') | ||
| e.preventDefault() | ||
| e.stopPropagation() |
+2
-2
| Metadata-Version: 2.4 | ||
| Name: wwwpy | ||
| Version: 0.1.81 | ||
| Version: 0.1.82 | ||
| Summary: Build Powerful Web Applications: Simple, Scalable, and Fully Customizable | ||
@@ -228,3 +228,3 @@ Author-email: Simone Giacomelli <simone.giacomelli@gmail.com> | ||
| Requires-Dist: tornado==6.4.2 | ||
| Requires-Dist: watchdog==4.0.2 | ||
| Requires-Dist: watchdog==6.0.0 | ||
| Requires-Dist: webtypy | ||
@@ -231,0 +231,0 @@ Provides-Extra: test |
+2
-2
| # https://packaging.python.org/en/latest/tutorials/packaging-projects/ | ||
| [project] | ||
| name = "wwwpy" | ||
| version = "0.1.81" | ||
| version = "0.1.82" | ||
@@ -14,3 +14,3 @@ # todo | ||
| authors = [{ name = "Simone Giacomelli", email = "simone.giacomelli@gmail.com" }] | ||
| dependencies = ["tornado==6.4.2", "watchdog==4.0.2", "webtypy"] | ||
| dependencies = ["tornado==6.4.2", "watchdog==6.0.0", "webtypy"] | ||
| requires-python = ">=3.9" | ||
@@ -17,0 +17,0 @@ keywords = ["wwwpy", "wasm", "pyodide", "web", "development", "dom", "html", "javascript"] |
| Metadata-Version: 2.4 | ||
| Name: wwwpy | ||
| Version: 0.1.81 | ||
| Version: 0.1.82 | ||
| Summary: Build Powerful Web Applications: Simple, Scalable, and Fully Customizable | ||
@@ -228,3 +228,3 @@ Author-email: Simone Giacomelli <simone.giacomelli@gmail.com> | ||
| Requires-Dist: tornado==6.4.2 | ||
| Requires-Dist: watchdog==4.0.2 | ||
| Requires-Dist: watchdog==6.0.0 | ||
| Requires-Dist: webtypy | ||
@@ -231,0 +231,0 @@ Provides-Extra: test |
| tornado==6.4.2 | ||
| watchdog==4.0.2 | ||
| watchdog==6.0.0 | ||
| webtypy | ||
@@ -4,0 +4,0 @@ |
@@ -132,2 +132,3 @@ LICENSE | ||
| src/wwwpy/remote/hotkey.py | ||
| src/wwwpy/remote/hotkeylib.py | ||
| src/wwwpy/remote/idbfs.py | ||
@@ -134,0 +135,0 @@ src/wwwpy/remote/jslib.py |
@@ -5,5 +5,7 @@ try: | ||
| __version__ = _build_meta.__version__ | ||
| __banner__ = f'wwwpy v{__version__}' | ||
| except: | ||
| __version__ = 'unknown' | ||
| __banner__ = 'wwwpy version unknown' | ||
| __all__ = ['__version__'] |
@@ -1,3 +0,3 @@ | ||
| __version__ = "0.1.81" | ||
| git_hash_short = "76bfe48" | ||
| git_hash = "76bfe48af6490bdfa59badd84f990eb6a29d9184" | ||
| __version__ = "0.1.82" | ||
| git_hash_short = "a26531e" | ||
| git_hash = "a26531eca4cdf1870bdc45098ee7dde970471ce5" |
@@ -28,2 +28,10 @@ from __future__ import annotations | ||
| def remove_class_attribute(source_code: str, class_name: str, attr_name: str) -> str: | ||
| source_code_imp = ensure_imports(source_code) | ||
| module = cst.parse_module(source_code_imp) | ||
| transformer = _RemoveFieldFromClassTransformer(class_name, attr_name) | ||
| modified_tree = module.visit(transformer) | ||
| return modified_tree.code | ||
| def rename_class_attribute(source_code: str, class_name: str, old_attr_name: str, new_attr_name: str): | ||
@@ -229,2 +237,27 @@ source_code_imp = ensure_imports(source_code) | ||
| class _RemoveFieldFromClassTransformer(cst.CSTTransformer): | ||
| def __init__(self, class_name, field_name): | ||
| super().__init__() | ||
| self.class_name = class_name | ||
| self.field_name = field_name | ||
| def leave_ClassDef(self, original_node, updated_node): | ||
| if original_node.name.value != self.class_name: | ||
| return original_node | ||
| new_body = [] | ||
| for item in updated_node.body.body: | ||
| # Check if this is an annotated assignment with the target field name | ||
| if (isinstance(item, cst.SimpleStatementLine) and | ||
| isinstance(item.body[0], cst.AnnAssign) and | ||
| isinstance(item.body[0].target, cst.Name) and | ||
| item.body[0].target.value == self.field_name): | ||
| # Skip this item to remove it | ||
| continue | ||
| else: | ||
| new_body.append(item) | ||
| return updated_node.with_changes(body=updated_node.body.with_changes(body=new_body)) | ||
| def add_method(source_code: str, class_name: str, method_name: str, method_args: str, | ||
@@ -231,0 +264,0 @@ instructions: str = 'pass') -> str: |
@@ -102,2 +102,7 @@ from __future__ import annotations | ||
| def __init__(self, element_path: ep.ElementPath, element_def: el.ElementDef): | ||
| self.element_path = element_path | ||
| self.element_def = element_def | ||
| self._init() | ||
| def _init(self): | ||
| self.attributes: ListMap[AttributeEditor] = ListMap(key_func=lambda attr: attr.definition.name) | ||
@@ -108,4 +113,2 @@ """One AttributeEditor for each attribute defined in the ElementDef.""" | ||
| self.element_path = element_path | ||
| self.element_def = element_def | ||
| self._fill_events() | ||
@@ -196,2 +199,5 @@ self._fill_attrs() | ||
| def _data_name_set_value(self, attribute_editor: AttributeEditor, value: str | None = ''): | ||
| if value == '': | ||
| self._attribute_remove(attribute_editor) | ||
| return | ||
| old_value = attribute_editor.value | ||
@@ -233,3 +239,10 @@ if self._node.content and old_value == self._node.content: | ||
| if attribute_editor.definition is _ad_data_name: | ||
| src = self.current_python_source() | ||
| src = code_edit.remove_class_attribute(src, self.element_path.class_name, attribute_editor.value) | ||
| self._write_source(src) | ||
| self._init() | ||
| # Create a translation table for the specified characters | ||
@@ -236,0 +249,0 @@ escape_table = str.maketrans({ |
| import dataclasses | ||
| import shutil | ||
| from dataclasses import dataclass, field | ||
| from pathlib import Path | ||
| from typing import List, Dict, Iterable, Union, Optional | ||
| from typing import List, Dict, Iterable, Optional | ||
| from wwwpy.common import tree | ||
| from wwwpy.common.filesystem.sync import Event | ||
| from dataclasses import dataclass, field | ||
@@ -108,9 +108,12 @@ | ||
| c = event.content | ||
| func = None | ||
| if isinstance(c, str): | ||
| path.write_text(c) | ||
| func = path.write_text | ||
| elif isinstance(c, bytes): | ||
| path.write_bytes(c) | ||
| func = path.write_bytes | ||
| else: | ||
| raise ValueError(f"Unsupported content type: {type(c)}") | ||
| path.parent.mkdir(parents=True, exist_ok=True) | ||
| func(c) | ||
@@ -117,0 +120,0 @@ def _get_content(path: Path): |
@@ -85,4 +85,4 @@ from __future__ import annotations | ||
| for f in b.body: | ||
| fqn = b.name + '.' + f.name | ||
| if isinstance(f, FunctionDef): | ||
| fqn = b.name + '.' + f.name | ||
| if len(f.args.args) > 1: # beyond self | ||
@@ -89,0 +89,0 @@ args = ', ' + ', '.join([a.arg for a in f.args.args[1:]]) |
@@ -7,2 +7,4 @@ import base64 | ||
| import re | ||
| import sys | ||
| import types | ||
| import typing | ||
@@ -28,3 +30,4 @@ from dataclasses import is_dataclass | ||
| origin = typing.get_origin(cls) | ||
| if origin is typing.Union: | ||
| # if origin is typing.Union or origin is types.UnionType: | ||
| if _is_union_type(origin): | ||
| args = set(get_args(cls)) | ||
@@ -100,3 +103,4 @@ obj_type = type(obj) | ||
| origin = get_origin(cls) | ||
| if origin is typing.Union: | ||
| # if origin is typing.Union: | ||
| if _is_union_type(origin): | ||
| args = set(get_args(cls)) | ||
@@ -202,1 +206,12 @@ obj_type = _get_type_from_string(data[0]) | ||
| raise ValueError(f"Expected object of type Result got {typ}") | ||
| def _is_union_type_3_10(origin): | ||
| return origin is typing.Union or origin is types.UnionType | ||
| def _is_union_type_3_9(origin): | ||
| return origin is typing.Union | ||
| _is_union_type = _is_union_type_3_10 if sys.version_info >= (3, 10) else _is_union_type_3_9 |
@@ -23,3 +23,4 @@ from __future__ import annotations | ||
| # print_tree('/wwwpy_bundle') | ||
| import wwwpy | ||
| console.log(wwwpy.__banner__) | ||
| await setup_websocket() | ||
@@ -26,0 +27,0 @@ dm.set_active(dev_mode) |
@@ -67,2 +67,3 @@ from __future__ import annotations | ||
| class ToolboxComponent(wpc.Component, tag_name='wwwpy-toolbox'): | ||
| _title: js.HTMLDivElement = wpc.element() | ||
| body: HTMLElement = wpc.element() | ||
@@ -104,3 +105,3 @@ inputSearch: js.HTMLInputElement = wpc.element() | ||
| <wwwpy-window data-name='_window'> | ||
| <div slot='title' style='text-align: center'>wwwpy - toolbox</div> | ||
| <div slot='title' data-name="_title" style='text-align: center'></div> | ||
| <div style="text-align: center; padding: 8px"> | ||
@@ -115,2 +116,4 @@ <button data-name="_select_element_btn">Select element...</button> | ||
| """ | ||
| import wwwpy | ||
| self._title.innerText = f'toolbox - {wwwpy.__banner__}' | ||
@@ -117,0 +120,0 @@ def connectedCallback(self): |
@@ -7,8 +7,9 @@ import threading | ||
| from watchdog.events import FileSystemEvent | ||
| from wwwpy.common.filesystem.sync import new_tmp_path | ||
| from wwwpy.common.filesystem.sync.event import Event | ||
| from wwwpy.server.filesystem_sync.any_observer import AnyObserver | ||
| from wwwpy.server.filesystem_sync.debouncer import Debouncer | ||
| from wwwpy.server.filesystem_sync.debouncer_thread import DebouncerThread | ||
| from wwwpy.common.filesystem.sync.event import Event | ||
| from watchdog.events import FileSystemEvent | ||
@@ -20,10 +21,8 @@ | ||
| self._debouncer = Debouncer(window) | ||
| self.skip_synthetic = True | ||
| self.skip_opened = True | ||
| super().__init__(self._debouncer, callback) | ||
| def skip_open(event: FileSystemEvent): | ||
| if event.event_type == 'opened' and self.skip_opened: | ||
| if event.event_type == 'opened' or event.event_type == 'closed_no_write': | ||
| return | ||
| if event.is_synthetic and self.skip_synthetic: | ||
| if event.is_synthetic: | ||
| return | ||
@@ -30,0 +29,0 @@ |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
756092
0.68%198
0.51%11288
1.08%