File: pyscript.core/src/stdlib/pyscript/fetch.py

import json

import js
from pyscript.util import as_bytearray


### wrap the response to grant Pythonic results
class _Response:
    def __init__(self, response):
        self._response = response

    # grant access to response.ok and other fields
    def __getattr__(self, attr):
        return getattr(self._response, attr)

    # exposed methods with Pythonic results
    async def arrayBuffer(self):
        buffer = await self._response.arrayBuffer()
        # works in Pyodide
        if hasattr(buffer, "to_py"):
            return buffer.to_py()
        # shims in MicroPython
        return memoryview(as_bytearray(buffer))

    async def blob(self):
        return await self._response.blob()

    async def bytearray(self):
        buffer = await self._response.arrayBuffer()
        return as_bytearray(buffer)

    async def json(self):
        return json.loads(await self.text())

    async def text(self):
        return await self._response.text()


### allow direct await to _Response methods
class _DirectResponse:
    @staticmethod
    def setup(promise, response):
        promise._response = _Response(response)
        return promise._response

    def __init__(self, promise):
        self._promise = promise
        promise._response = None
        promise.arrayBuffer = self.arrayBuffer
        promise.blob = self.blob
        promise.bytearray = self.bytearray
        promise.json = self.json
        promise.text = self.text

    async def _response(self):
        if not self._promise._response:
            await self._promise
        return self._promise._response

    async def arrayBuffer(self):
        response = await self._response()
        return await response.arrayBuffer()

    async def blob(self):
        response = await self._response()
        return await response.blob()

    async def bytearray(self):
        response = await self._response()
        return await response.bytearray()

    async def json(self):
        response = await self._response()
        return await response.json()

    async def text(self):
        response = await self._response()
        return await response.text()


def fetch(url, **kw):
    # workaround Pyodide / MicroPython dict <-> js conversion
    options = js.JSON.parse(json.dumps(kw))
    awaited = lambda response, *args: _DirectResponse.setup(promise, response)
    promise = js.fetch(url, options).then(awaited)
    _DirectResponse(promise)
    return promise



File: pyscript.core/src/stdlib/pyscript/web.py

"""Lightweight interface to the DOM and HTML elements."""

# `when` is not used in this module. It is imported here save the user an additional
# import (i.e. they can get what they need from `pyscript.web`).
from pyscript import document, when  # NOQA


def wrap_dom_element(dom_element):
    """Wrap an existing DOM element in an instance of a subclass of `Element`.

    This is just a convenience function to avoid having to import the `Element` class
    and use its class method.
    """

    return Element.wrap_dom_element(dom_element)


class Element:
    # A lookup table to get an `Element` subclass by tag name. Used when wrapping an
    # existing DOM element.
    element_classes_by_tag_name = {}

    @classmethod
    def get_tag_name(cls):
        """Return the HTML tag name for the class.

        For classes that have a trailing underscore (because they clash with a Python
        keyword or built-in), we remove it to get the tag name. e.g. for the `input_`
        class, the tag name is `input`.

        """
        return cls.__name__.replace("_", "")

    @classmethod
    def register_element_classes(cls, element_classes):
        """Register an iterable of element classes."""
        for element_class in element_classes:
            tag_name = element_class.get_tag_name()
            cls.element_classes_by_tag_name[tag_name] = element_class

    @classmethod
    def unregister_element_classes(cls, element_classes):
        """Unregister an iterable of element classes."""
        for element_class in element_classes:
            tag_name = element_class.get_tag_name()
            cls.element_classes_by_tag_name.pop(tag_name, None)

    @classmethod
    def wrap_dom_element(cls, dom_element):
        """Wrap an existing DOM element in an instance of a subclass of `Element`.

        We look up the `Element` subclass by the DOM element's tag name. For any unknown
        elements (custom tags etc.) use *this* class (`Element`).
        """
        element_cls = cls.element_classes_by_tag_name.get(
            dom_element.tagName.lower(), cls
        )

        return element_cls(dom_element=dom_element)

    def __init__(self, dom_element=None, classes=None, style=None, **kwargs):
        """Create a new, or wrap an existing DOM element.

        If `dom_element` is None we are being called to *create* a new element.
        Otherwise, we are being called to *wrap* an existing DOM element.
        """
        self._dom_element = dom_element or document.createElement(
            type(self).get_tag_name()
        )

        # A set-like interface to the element's `classList`.
        self._classes = Classes(self)

        # A dict-like interface to the element's `style` attribute.
        self._style = Style(self)

        # Set any specified classes, styles, and DOM properties.
        self.update(classes=classes, style=style, **kwargs)

    def __eq__(self, obj):
        """Check for equality by comparing the underlying DOM element."""
        return isinstance(obj, Element) and obj._dom_element == self._dom_element

    def __getitem__(self, key):
        """Get an item within the element's children.

        If `key` is an integer or a slice we use it to index/slice the element's
        children. Otherwise, we use `key` as a query selector.
        """
        if isinstance(key, int) or isinstance(key, slice):
            return self.children[key]

        return self.find(key)

    def __getattr__(self, name):
        # This allows us to get attributes on the underlying DOM element that clash
        # with Python keywords or built-ins (e.g. the output element has an
        # attribute `for` which is a Python keyword, so you can access it on the
        # Element instance via `for_`).
        if name.endswith("_"):
            name = name[:-1]

        return getattr(self._dom_element, name)

    def __setattr__(self, name, value):
        # This class overrides `__setattr__` to delegate "public" attributes to the
        # underlying DOM element. BUT, we don't use the usual Python pattern where
        # we set attributes on the element itself via `self.__dict__` as that is not
        # yet supported in our build of MicroPython. Instead, we handle it here by
        # using super for all "private" attributes (those starting with an underscore).
        if name.startswith("_"):
            super().__setattr__(name, value)

        else:
            # This allows us to set attributes on the underlying DOM element that clash
            # with Python keywords or built-ins (e.g. the output element has an
            # attribute `for` which is a Python keyword, so you can access it on the
            # Element instance via `for_`).
            if name.endswith("_"):
                name = name[:-1]

            setattr(self._dom_element, name, value)

    @property
    def children(self):
        """Return the element's children as an `ElementCollection`."""
        return ElementCollection.wrap_dom_elements(self._dom_element.children)

    @property
    def classes(self):
        """Return the element's `classList` as a `Classes` instance."""
        return self._classes

    @property
    def parent(self):
        """Return the element's `parent `Element`."""
        if self._dom_element.parentElement is None:
            return None

        return Element.wrap_dom_element(self._dom_element.parentElement)

    @property
    def style(self):
        """Return the element's `style` attribute as a `Style` instance."""
        return self._style

    def append(self, *items):
        """Append the specified items to the element."""
        for item in items:
            if isinstance(item, Element):
                self._dom_element.appendChild(item._dom_element)

            elif isinstance(item, ElementCollection):
                for element in item:
                    self._dom_element.appendChild(element._dom_element)

            # We check for list/tuple here and NOT for any iterable as it will match
            # a JS Nodelist which is handled explicitly below.
            # NodeList.
            elif isinstance(item, list) or isinstance(item, tuple):
                for child in item:
                    self.append(child)

            else:
                # In this case we know it's not an Element or an ElementCollection, so
                # we guess that it's either a DOM element or NodeList returned via the
                # ffi.
                try:
                    # First, we try to see if it's an element by accessing the 'tagName'
                    # attribute.
                    item.tagName
                    self._dom_element.appendChild(item)

                except AttributeError:
                    try:
                        # Ok, it's not an element, so let's see if it's a NodeList by
                        # accessing the 'length' attribute.
                        item.length
                        for element_ in item:
                            self._dom_element.appendChild(element_)

                    except AttributeError:
                        # Nope! This is not an element or a NodeList.
                        raise TypeError(
                            f'Element "{item}" is a proxy object, "'
                            f"but not a valid element or a NodeList."
                        )

    def clone(self, clone_id=None):
        """Make a clone of the element (clones the underlying DOM object too)."""
        clone = Element.wrap_dom_element(self._dom_element.cloneNode(True))
        clone.id = clone_id
        return clone

    def find(self, selector):
        """Find all elements that match the specified selector.

        Return the results as a (possibly empty) `ElementCollection`.
        """
        return ElementCollection.wrap_dom_elements(
            self._dom_element.querySelectorAll(selector)
        )

    def show_me(self):
        """Convenience method for 'element.scrollIntoView()'."""
        self._dom_element.scrollIntoView()

    def update(self, classes=None, style=None, **kwargs):
        """Update the element with the specified classes, styles, and DOM properties."""

        if classes:
            self.classes.add(classes)

        if style:
            self.style.set(**style)

        for name, value in kwargs.items():
            setattr(self, name, value)


class Classes:
    """A set-like interface to an element's `classList`."""

    def __init__(self, element: Element):
        self._element = element
        self._class_list = self._element._dom_element.classList

    def __contains__(self, item):
        return item in self._class_list

    def __eq__(self, other):
        # We allow comparison with either another `Classes` instance...
        if isinstance(other, Classes):
            compare_with = list(other._class_list)

        # ...or iterables of strings.
        else:
            # TODO: Check MP for existence of better iterable test.
            try:
                compare_with = iter(other)

            except TypeError:
                return False

        return set(self._class_list) == set(compare_with)

    def __iter__(self):
        return iter(self._class_list)

    def __len__(self):
        return self._class_list.length

    def __repr__(self):
        return f"Classes({', '.join(self._class_list)})"

    def __str__(self):
        return " ".join(self._class_list)

    def add(self, *class_names):
        """Add one or more classes to the element."""
        for class_name in class_names:
            if isinstance(class_name, list):
                for item in class_name:
                    self.add(item)

            else:
                self._class_list.add(class_name)

    def contains(self, class_name):
        """Check if the element has the specified class."""
        return class_name in self

    def remove(self, *class_names):
        """Remove one or more classes from the element."""
        for class_name in class_names:
            if isinstance(class_name, list):
                for item in class_name:
                    self.remove(item)

            else:
                self._class_list.remove(class_name)

    def replace(self, old_class, new_class):
        """Replace one of the element's classes with another."""
        self.remove(old_class)
        self.add(new_class)

    def toggle(self, *class_names):
        """Toggle one or more of the element's classes."""
        for class_name in class_names:
            if class_name in self:
                self.remove(class_name)

            else:
                self.add(class_name)


class HasOptions:
    """Mix-in for elements that have an options attribute.

    The elements that support options are: <datalist>, <optgroup>, and <select>.
    """

    @property
    def options(self):
        """Return the element's options as an `Options"""
        if not hasattr(self, "_options"):
            self._options = Options(self)

        return self._options


class Options:
    """This class represents the <option>s of a <datalist>, <optgroup> or <select>.

    It allows access to add and remove <option>s by using the `add`, `remove` and
    `clear` methods.
    """

    def __init__(self, element):
        self._element = element

    def __getitem__(self, key):
        return self.options[key]

    def __iter__(self):
        yield from self.options

    def __len__(self):
        return len(self.options)

    def __repr__(self):
        return f"{self.__class__.__name__} (length: {len(self)}) {self.options}"

    @property
    def options(self):
        """Return the list of options."""
        return [Element.wrap_dom_element(o) for o in self._element._dom_element.options]

    @property
    def selected(self):
        """Return the selected option."""
        return self.options[self._element._dom_element.selectedIndex]

    def add(self, value=None, html=None, text=None, before=None, **kwargs):
        """Add a new option to the element"""
        if value is not None:
            kwargs["value"] = value

        if html is not None:
            kwargs["innerHTML"] = html

        if text is not None:
            kwargs["text"] = text

        new_option = option(**kwargs)

        if before:
            if isinstance(before, Element):
                before = before._dom_element

        self._element._dom_element.add(new_option._dom_element, before)

    def clear(self):
        """Remove all options."""
        while len(self) > 0:
            self.remove(0)

    def remove(self, index):
        """Remove the option at the specified index."""
        self._element._dom_element.remove(index)


class Style:
    """A dict-like interface to an element's `style` attribute."""

    def __init__(self, element: Element):
        self._element = element
        self._style = self._element._dom_element.style

    def __getitem__(self, key):
        return self._style.getPropertyValue(key)

    def __setitem__(self, key, value):
        self._style.setProperty(key, value)

    def remove(self, key):
        """Remove a CSS property from the element."""
        self._style.removeProperty(key)

    def set(self, **kwargs):
        """Set one or more CSS properties on the element."""
        for key, value in kwargs.items():
            self._element._dom_element.style.setProperty(key, value)

    # CSS Properties
    # Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2
    # Following properties automatically generated from the above reference using
    # tools/codegen_css_proxy.py
    @property
    def visible(self):
        return self._element._dom_element.style.visibility

    @visible.setter
    def visible(self, value):
        self._element._dom_element.style.visibility = value


class ContainerElement(Element):
    """Base class for elements that can contain other elements."""

    def __init__(
        self, *args, children=None, dom_element=None, style=None, classes=None, **kwargs
    ):
        super().__init__(
            dom_element=dom_element, style=style, classes=classes, **kwargs
        )

        for child in list(args) + (children or []):
            if isinstance(child, Element) or isinstance(child, ElementCollection):
                self.append(child)

            else:
                self._dom_element.insertAdjacentHTML("beforeend", child)

    def __iter__(self):
        yield from self.children


class ClassesCollection:
    """A set-like interface to the classes of the elements in a collection."""

    def __init__(self, collection):
        self._collection = collection

    def __contains__(self, class_name):
        for element in self._collection:
            if class_name in element.classes:
                return True

        return False

    def __eq__(self, other):
        return (
            isinstance(other, ClassesCollection)
            and self._collection == other._collection
        )

    def __iter__(self):
        for class_name in self._all_class_names():
            yield class_name

    def __len__(self):
        return len(self._all_class_names())

    def __repr__(self):
        return f"ClassesCollection({repr(self._collection)})"

    def __str__(self):
        return " ".join(self._all_class_names())

    def add(self, *class_names):
        """Add one or more classes to the elements in the collection."""
        for element in self._collection:
            element.classes.add(*class_names)

    def contains(self, class_name):
        """Check if any element in the collection has the specified class."""
        return class_name in self

    def remove(self, *class_names):
        """Remove one or more classes from the elements in the collection."""

        for element in self._collection:
            element.classes.remove(*class_names)

    def replace(self, old_class, new_class):
        """Replace one of the classes in the elements in the collection with another."""
        for element in self._collection:
            element.classes.replace(old_class, new_class)

    def toggle(self, *class_names):
        """Toggle one or more classes on the elements in the collection."""
        for element in self._collection:
            element.classes.toggle(*class_names)

    def _all_class_names(self):
        all_class_names = set()
        for element in self._collection:
            for class_name in element.classes:
                all_class_names.add(class_name)

        return all_class_names


class StyleCollection:
    """A dict-like interface to the styles of the elements in a collection."""

    def __init__(self, collection):
        self._collection = collection

    def __getitem__(self, key):
        return [element.style[key] for element in self._collection._elements]

    def __setitem__(self, key, value):
        for element in self._collection._elements:
            element.style[key] = value

    def __repr__(self):
        return f"StyleCollection({repr(self._collection)})"

    def remove(self, key):
        """Remove a CSS property from the elements in the collection."""
        for element in self._collection._elements:
            element.style.remove(key)


class ElementCollection:
    @classmethod
    def wrap_dom_elements(cls, dom_elements):
        """Wrap an iterable of dom_elements in an `ElementCollection`."""

        return cls(
            [Element.wrap_dom_element(dom_element) for dom_element in dom_elements]
        )

    def __init__(self, elements: [Element]):
        self._elements = elements
        self._classes = ClassesCollection(self)
        self._style = StyleCollection(self)

    def __eq__(self, obj):
        """Check for equality by comparing the underlying DOM elements."""
        return isinstance(obj, ElementCollection) and obj._elements == self._elements

    def __getitem__(self, key):
        """Get an item in the collection.

        If `key` is an integer or a slice we use it to index/slice the collection.
        Otherwise, we use `key` as a query selector.
        """
        if isinstance(key, int):
            return self._elements[key]

        elif isinstance(key, slice):
            return ElementCollection(self._elements[key])

        return self.find(key)

    def __iter__(self):
        yield from self._elements

    def __len__(self):
        return len(self._elements)

    def __repr__(self):
        return (
            f"{self.__class__.__name__} (length: {len(self._elements)}) "
            f"{self._elements}"
        )

    def __getattr__(self, name):
        return [getattr(element, name) for element in self._elements]

    def __setattr__(self, name, value):
        # This class overrides `__setattr__` to delegate "public" attributes to the
        # elements in the collection. BUT, we don't use the usual Python pattern where
        # we set attributes on the collection itself via `self.__dict__` as that is not
        # yet supported in our build of MicroPython. Instead, we handle it here by
        # using super for all "private" attributes (those starting with an underscore).
        if name.startswith("_"):
            super().__setattr__(name, value)

        else:
            for element in self._elements:
                setattr(element, name, value)

    @property
    def classes(self):
        """Return the classes of the elements in the collection as a `ClassesCollection`."""
        return self._classes

    @property
    def elements(self):
        """Return the elements in the collection as a list."""
        return self._elements

    @property
    def style(self):
        """"""
        return self._style

    def find(self, selector):
        """Find all elements that match the specified selector.

        Return the results as a (possibly empty) `ElementCollection`.
        """
        elements = []
        for element in self._elements:
            elements.extend(element.find(selector))

        return ElementCollection(elements)


# Classes for every HTML element. If the element tag name (e.g. "input") clashes with
# either a Python keyword or common symbol, then we suffix the class name with an "_"
# (e.g. the class for the "input" element is "input_").


class a(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a"""


class abbr(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr"""


class address(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address"""


class area(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area"""


class article(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article"""


class aside(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside"""


class audio(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"""


class b(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b"""


class base(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base"""


class blockquote(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"""


class body(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body"""


class br(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br"""


class button(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button"""


class canvas(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas"""

    def download(self, filename: str = "snapped.png"):
        """Download the current element with the filename provided in input.

        Inputs:
            * filename (str): name of the file being downloaded

        Output:
            None
        """
        download_link = a(download=filename, href=self._dom_element.toDataURL())

        # Adding the link to the DOM is recommended for browser compatibility to make
        # sure that the click works.
        self.append(download_link)

        download_link._dom_element.click()

    def draw(self, what, width=None, height=None):
        """Draw `what` on the current element

        Inputs:

            * what (canvas image source): An element to draw into the context. The
                specification permits any canvas image source, specifically, an
                HTMLImageElement, an SVGImageElement, an HTMLVideoElement,
                an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a
                VideoFrame.
        """
        if isinstance(what, Element):
            what = what._dom_element

        # https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
        ctx = self._dom_element.getContext("2d")
        if width or height:
            ctx.drawImage(what, 0, 0, width, height)

        else:
            ctx.drawImage(what, 0, 0)


class caption(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption"""


class cite(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite"""


class code(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code"""


class col(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col"""


class colgroup(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup"""


class data(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data"""


class datalist(ContainerElement, HasOptions):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist"""


class dd(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd"""


class del_(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del"""


class details(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"""


class dialog(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"""


class div(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div"""


class dl(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl"""


class dt(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt"""


class em(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em"""


class embed(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed"""


class fieldset(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset"""


class figcaption(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption"""


class figure(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure"""


class footer(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer"""


class form(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form"""


class h1(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1"""


class h2(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2"""


class h3(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3"""


class h4(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4"""


class h5(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5"""


class h6(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6"""


class head(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head"""


class header(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header"""


class hgroup(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup"""


class hr(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr"""


class html(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html"""


class i(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i"""


class iframe(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"""


class img(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"""


class input_(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"""


class ins(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins"""


class kbd(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd"""


class label(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label"""


class legend(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend"""


class li(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li"""


class link(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link"""


class main(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main"""


class map_(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map"""


class mark(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark"""


class menu(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu"""


class meta(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta"""


class meter(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter"""


class nav(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav"""


class object_(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object"""


class ol(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol"""


class optgroup(ContainerElement, HasOptions):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup"""


class option(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option"""


class output(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output"""


class p(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p"""


class param(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param"""


class picture(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture"""


class pre(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre"""


class progress(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress"""


class q(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q"""


class s(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s"""


class script(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"""


class section(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section"""


class select(ContainerElement, HasOptions):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"""


class small(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small"""


class source(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source"""


class span(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span"""


class strong(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong"""


class style(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style"""


class sub(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub"""


class summary(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary"""


class sup(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup"""


class table(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table"""


class tbody(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody"""


class td(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td"""


class template(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template"""


class textarea(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea"""


class tfoot(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot"""


class th(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th"""


class thead(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead"""


class time(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time"""


class title(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title"""


class tr(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr"""


class track(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track"""


class u(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u"""


class ul(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul"""


class var(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var"""


class video(ContainerElement):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"""

    def snap(
        self,
        to: Element | str = None,
        width: int | None = None,
        height: int | None = None,
    ):
        """
        Capture a snapshot (i.e. a single frame) of a video to a canvas.

        Inputs:

            * to: the canvas to save the video frame to (if None, one is created).
            * width: width of the snapshot (defaults to the video width).
            * height: height of the snapshot (defaults to the video height).

        Output:
            (Element) canvas element where the video frame snapshot was drawn into
        """
        width = width if width is not None else self.videoWidth
        height = height if height is not None else self.videoHeight

        if to is None:
            to = canvas(width=width, height=height)

        elif isinstance(to, Element):
            if to.tag != "canvas":
                raise TypeError("Element to snap to must be a canvas.")

        elif getattr(to, "tagName", "") == "CANVAS":
            to = canvas(dom_element=to)

        # If 'to' is a string, then assume it is a query selector.
        elif isinstance(to, str):
            nodelist = document.querySelectorAll(to)  # NOQA
            if nodelist.length == 0:
                raise TypeError("No element with selector {to} to snap to.")

            if nodelist[0].tagName != "CANVAS":
                raise TypeError("Element to snap to must be a canvas.")

            to = canvas(dom_element=nodelist[0])

        to.draw(self, width, height)

        return to


class wbr(Element):
    """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr"""


# fmt: off
ELEMENT_CLASSES = [
    a, abbr, address, area, article, aside, audio,
    b, base, blockquote, body, br, button,
    canvas, caption, cite, code, col, colgroup,
    data, datalist, dd, del_, details, dialog, div, dl, dt,
    em, embed,
    fieldset, figcaption, figure, footer, form,
    h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html,
    i, iframe, img, input_, ins,
    kbd,
    label, legend, li, link,
    main, map_, mark, menu, meta, meter,
    nav,
    object_, ol, optgroup, option, output,
    p, param, picture, pre, progress,
    q,
    s, script, section, select, small, source, span, strong, style, sub, summary, sup,
    table, tbody, td, template, textarea, tfoot, th, thead, time, title, tr, track,
    u, ul,
    var, video,
    wbr,
]
# fmt: on


# Register all the default (aka "built-in") Element classes.
Element.register_element_classes(ELEMENT_CLASSES)


class Page:
    """Represents the whole page."""

    def __init__(self):
        self.html = Element.wrap_dom_element(document.documentElement)
        self.body = Element.wrap_dom_element(document.body)
        self.head = Element.wrap_dom_element(document.head)

    def __getitem__(self, selector):
        """Get an item on the page.

        We don't index/slice the page like we do with `Element` and `ElementCollection`
        as it is a bit muddier what the ideal behavior should be. Instead, we simply
        use this as a convenience method to `find` elements on the page.
        """
        return self.find(selector)

    @property
    def title(self):
        """Return the page title."""
        return document.title

    @title.setter
    def title(self, value):
        """Set the page title."""
        document.title = value

    def append(self, *items):
        """Shortcut for `page.body.append`."""
        self.body.append(*items)

    def find(self, selector):  # NOQA
        """Find all elements that match the specified selector.

        Return the results as a (possibly empty) `ElementCollection`.
        """
        return ElementCollection.wrap_dom_elements(document.querySelectorAll(selector))


page = Page()



File: pyscript.core/src/stdlib/pyscript/util.py

import js


def as_bytearray(buffer):
    ui8a = js.Uint8Array.new(buffer)
    size = ui8a.length
    ba = bytearray(size)
    for i in range(0, size):
        ba[i] = ui8a[i]
    return ba


class NotSupported:
    """
    Small helper that raises exceptions if you try to get/set any attribute on
    it.
    """

    def __init__(self, name, error):
        object.__setattr__(self, "name", name)
        object.__setattr__(self, "error", error)

    def __repr__(self):
        return f"<NotSupported {self.name} [{self.error}]>"

    def __getattr__(self, attr):
        raise AttributeError(self.error)

    def __setattr__(self, attr, value):
        raise AttributeError(self.error)

    def __call__(self, *args):
        raise TypeError(self.error)



File: pyscript.core/src/stdlib/pyscript/magic_js.py

import json
import sys

import js as globalThis
from polyscript import config as _config
from polyscript import js_modules
from pyscript.util import NotSupported

RUNNING_IN_WORKER = not hasattr(globalThis, "document")

config = json.loads(globalThis.JSON.stringify(_config))


# allow `from pyscript.js_modules.xxx import yyy`
class JSModule:
    def __init__(self, name):
        self.name = name

    def __getattr__(self, field):
        # avoid pyodide looking for non existent fields
        if not field.startswith("_"):
            return getattr(getattr(js_modules, self.name), field)


# generate N modules in the system that will proxy the real value
for name in globalThis.Reflect.ownKeys(js_modules):
    sys.modules[f"pyscript.js_modules.{name}"] = JSModule(name)
sys.modules["pyscript.js_modules"] = js_modules

if RUNNING_IN_WORKER:
    import polyscript

    PyWorker = NotSupported(
        "pyscript.PyWorker",
        "pyscript.PyWorker works only when running in the main thread",
    )

    try:
        import js

        window = polyscript.xworker.window
        document = window.document
        js.document = document
        # this is the same as js_import on main and it lands modules on main
        js_import = window.Function(
            "return (...urls) => Promise.all(urls.map((url) => import(url)))"
        )()
    except:
        message = "Unable to use `window` or `document` -> https://docs.pyscript.net/latest/faq/#sharedarraybuffer"
        globalThis.console.warn(message)
        window = NotSupported("pyscript.window", message)
        document = NotSupported("pyscript.document", message)
        js_import = None

    sync = polyscript.xworker.sync

    # in workers the display does not have a default ID
    # but there is a sync utility from xworker
    def current_target():
        return polyscript.target

else:
    import _pyscript
    from _pyscript import PyWorker, js_import

    window = globalThis
    document = globalThis.document
    sync = NotSupported(
        "pyscript.sync", "pyscript.sync works only when running in a worker"
    )

    # in MAIN the current element target exist, just use it
    def current_target():
        return _pyscript.target



File: pyscript.core/src/stdlib/pyscript/__init__.py

# Some notes about the naming conventions and the relationship between various
# similar-but-different names.
#
# import pyscript
#     this package contains the main user-facing API offered by pyscript. All
#     the names which are supposed be used by end users should be made
#     available in pyscript/__init__.py (i.e., this file)
#
# import _pyscript
#     this is an internal module implemented in JS. It is used internally by
#     the pyscript package, end users should not use it directly. For its
#     implementation, grep for `interpreter.registerJsModule("_pyscript",
#     ...)` in core.js
#
# import js
#     this is the JS globalThis, as exported by pyodide and/or micropython's
#     FFIs. As such, it contains different things in the main thread or in a
#     worker.
#
# import pyscript.magic_js
#     this submodule abstracts away some of the differences between the main
#     thread and the worker. In particular, it defines `window` and `document`
#     in such a way that these names work in both cases: in the main thread,
#     they are the "real" objects, in the worker they are proxies which work
#     thanks to coincident.
#
# from pyscript import window, document
#     these are just the window and document objects as defined by
#     pyscript.magic_js. This is the blessed way to access them from pyscript,
#     as it works transparently in both the main thread and worker cases.

from polyscript import lazy_py_modules as py_import
from pyscript.display import HTML, display
from pyscript.fetch import fetch
from pyscript.magic_js import (
    RUNNING_IN_WORKER,
    PyWorker,
    config,
    current_target,
    document,
    js_import,
    js_modules,
    sync,
    window,
)
from pyscript.storage import Storage, storage
from pyscript.websocket import WebSocket

if not RUNNING_IN_WORKER:
    from pyscript.workers import create_named_worker, workers

try:
    from pyscript.event_handling import when
except:
    # TODO: should we remove this? Or at the very least, we should capture
    # the traceback otherwise it's very hard to debug
    from pyscript.util import NotSupported

    when = NotSupported(
        "pyscript.when", "pyscript.when currently not available with this interpreter"
    )



File: pyscript.core/src/stdlib/pyscript/flatted.py

# https://www.npmjs.com/package/flatted

import json as _json


class _Known:
    def __init__(self):
        self.key = []
        self.value = []


class _String:
    def __init__(self, value):
        self.value = value


def _array_keys(value):
    keys = []
    i = 0
    for _ in value:
        keys.append(i)
        i += 1
    return keys


def _object_keys(value):
    keys = []
    for key in value:
        keys.append(key)
    return keys


def _is_array(value):
    return isinstance(value, list) or isinstance(value, tuple)


def _is_object(value):
    return isinstance(value, dict)


def _is_string(value):
    return isinstance(value, str)


def _index(known, input, value):
    input.append(value)
    index = str(len(input) - 1)
    known.key.append(value)
    known.value.append(index)
    return index


def _loop(keys, input, known, output):
    for key in keys:
        value = output[key]
        if isinstance(value, _String):
            _ref(key, input[int(value.value)], input, known, output)

    return output


def _ref(key, value, input, known, output):
    if _is_array(value) and not value in known:
        known.append(value)
        value = _loop(_array_keys(value), input, known, value)
    elif _is_object(value) and not value in known:
        known.append(value)
        value = _loop(_object_keys(value), input, known, value)

    output[key] = value


def _relate(known, input, value):
    if _is_string(value) or _is_array(value) or _is_object(value):
        try:
            return known.value[known.key.index(value)]
        except:
            return _index(known, input, value)

    return value


def _transform(known, input, value):
    if _is_array(value):
        output = []
        for val in value:
            output.append(_relate(known, input, val))
        return output

    if _is_object(value):
        obj = {}
        for key in value:
            obj[key] = _relate(known, input, value[key])
        return obj

    return value


def _wrap(value):
    if _is_string(value):
        return _String(value)

    if _is_array(value):
        i = 0
        for val in value:
            value[i] = _wrap(val)
            i += 1

    elif _is_object(value):
        for key in value:
            value[key] = _wrap(value[key])

    return value


def parse(value, *args, **kwargs):
    json = _json.loads(value, *args, **kwargs)
    wrapped = []
    for value in json:
        wrapped.append(_wrap(value))

    input = []
    for value in wrapped:
        if isinstance(value, _String):
            input.append(value.value)
        else:
            input.append(value)

    value = input[0]

    if _is_array(value):
        return _loop(_array_keys(value), input, [value], value)

    if _is_object(value):
        return _loop(_object_keys(value), input, [value], value)

    return value


def stringify(value, *args, **kwargs):
    known = _Known()
    input = []
    output = []
    i = int(_index(known, input, value))
    while i < len(input):
        output.append(_transform(known, input, input[i]))
        i += 1
    return _json.dumps(output, *args, **kwargs)



File: pyscript.core/src/stdlib/pyscript/event_handling.py

import inspect

try:
    from pyodide.ffi.wrappers import add_event_listener

except ImportError:

    def add_event_listener(el, event_type, func):
        el.addEventListener(event_type, func)


from pyscript.magic_js import document


def when(event_type=None, selector=None):
    """
    Decorates a function and passes py-* events to the decorated function
    The events might or not be an argument of the decorated function
    """

    def decorator(func):

        from pyscript.web import Element, ElementCollection

        if isinstance(selector, str):
            elements = document.querySelectorAll(selector)
        # TODO: This is a hack that will be removed when pyscript becomes a package
        #       and we can better manage the imports without circular dependencies
        elif isinstance(selector, Element):
            elements = [selector._dom_element]
        elif isinstance(selector, ElementCollection):
            elements = [el._dom_element for el in selector]
        else:
            if isinstance(selector, list):
                elements = selector
            else:
                elements = [selector]

        try:
            sig = inspect.signature(func)
            # Function doesn't receive events
            if not sig.parameters:

                # Function is async: must be awaited
                if inspect.iscoroutinefunction(func):

                    async def wrapper(*args, **kwargs):
                        await func()

                else:

                    def wrapper(*args, **kwargs):
                        func()

            else:
                wrapper = func

        except AttributeError:
            # TODO: this is very ugly hack to get micropython working because inspect.signature
            #       doesn't exist, but we need to actually properly replace inspect.signature.
            #       It may be actually better to not try any magic for now and raise the error
            def wrapper(*args, **kwargs):
                try:
                    return func(*args, **kwargs)
                except TypeError as e:
                    if "takes" in str(e) and "positional arguments" in str(e):
                        return func()

                    raise

        for el in elements:
            add_event_listener(el, event_type, wrapper)

        return func

    return decorator



File: pyscript.core/src/stdlib/pyscript/display.py

import base64
import html
import io
import re

from pyscript.magic_js import current_target, document, window

_MIME_METHODS = {
    "savefig": "image/png",
    "_repr_javascript_": "application/javascript",
    "_repr_json_": "application/json",
    "_repr_latex": "text/latex",
    "_repr_png_": "image/png",
    "_repr_jpeg_": "image/jpeg",
    "_repr_pdf_": "application/pdf",
    "_repr_svg_": "image/svg+xml",
    "_repr_markdown_": "text/markdown",
    "_repr_html_": "text/html",
    "__repr__": "text/plain",
}


def _render_image(mime, value, meta):
    # If the image value is using bytes we should convert it to base64
    # otherwise it will return raw bytes and the browser will not be able to
    # render it.
    if isinstance(value, bytes):
        value = base64.b64encode(value).decode("utf-8")

    # This is the pattern of base64 strings
    base64_pattern = re.compile(
        r"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"
    )
    # If value doesn't match the base64 pattern we should encode it to base64
    if len(value) > 0 and not base64_pattern.match(value):
        value = base64.b64encode(value.encode("utf-8")).decode("utf-8")

    data = f"data:{mime};charset=utf-8;base64,{value}"
    attrs = " ".join(['{k}="{v}"' for k, v in meta.items()])
    return f'<img src="{data}" {attrs}></img>'


def _identity(value, meta):
    return value


_MIME_RENDERERS = {
    "text/plain": html.escape,
    "text/html": _identity,
    "image/png": lambda value, meta: _render_image("image/png", value, meta),
    "image/jpeg": lambda value, meta: _render_image("image/jpeg", value, meta),
    "image/svg+xml": _identity,
    "application/json": _identity,
    "application/javascript": lambda value, meta: f"<script>{value}<\\/script>",
}


class HTML:
    """
    Wrap a string so that display() can render it as plain HTML
    """

    def __init__(self, html):
        self._html = html

    def _repr_html_(self):
        return self._html


def _eval_formatter(obj, print_method):
    """
    Evaluates a formatter method.
    """
    if print_method == "__repr__":
        return repr(obj)
    elif hasattr(obj, print_method):
        if print_method == "savefig":
            buf = io.BytesIO()
            obj.savefig(buf, format="png")
            buf.seek(0)
            return base64.b64encode(buf.read()).decode("utf-8")
        return getattr(obj, print_method)()
    elif print_method == "_repr_mimebundle_":
        return {}, {}
    return None


def _format_mime(obj):
    """
    Formats object using _repr_x_ methods.
    """
    if isinstance(obj, str):
        return html.escape(obj), "text/plain"

    mimebundle = _eval_formatter(obj, "_repr_mimebundle_")
    if isinstance(mimebundle, tuple):
        format_dict, _ = mimebundle
    else:
        format_dict = mimebundle

    output, not_available = None, []
    for method, mime_type in _MIME_METHODS.items():
        if mime_type in format_dict:
            output = format_dict[mime_type]
        else:
            output = _eval_formatter(obj, method)

        if output is None:
            continue
        elif mime_type not in _MIME_RENDERERS:
            not_available.append(mime_type)
            continue
        break
    if output is None:
        if not_available:
            window.console.warn(
                f"Rendered object requested unavailable MIME renderers: {not_available}"
            )
        output = repr(output)
        mime_type = "text/plain"
    elif isinstance(output, tuple):
        output, meta = output
    else:
        meta = {}
    return _MIME_RENDERERS[mime_type](output, meta), mime_type


def _write(element, value, append=False):
    html, mime_type = _format_mime(value)
    if html == "\\n":
        return

    if append:
        out_element = document.createElement("div")
        element.append(out_element)
    else:
        out_element = element.lastElementChild
        if out_element is None:
            out_element = element

    if mime_type in ("application/javascript", "text/html"):
        script_element = document.createRange().createContextualFragment(html)
        out_element.append(script_element)
    else:
        out_element.innerHTML = html


def display(*values, target=None, append=True):
    if target is None:
        target = current_target()
    elif not isinstance(target, str):
        raise TypeError(f"target must be str or None, not {target.__class__.__name__}")
    elif target == "":
        raise ValueError("Cannot have an empty target")
    elif target.startswith("#"):
        # note: here target is str and not None!
        # align with @when behavior
        target = target[1:]

    element = document.getElementById(target)

    # If target cannot be found on the page, a ValueError is raised
    if element is None:
        raise ValueError(
            f"Invalid selector with id={target}. Cannot be found in the page."
        )

    # if element is a <script type="py">, it has a 'target' attribute which
    # points to the visual element holding the displayed values. In that case,
    # use that.
    if element.tagName == "SCRIPT" and hasattr(element, "target"):
        element = element.target

    for v in values:
        if not append:
            element.replaceChildren()
        _write(element, v, append=append)



File: pyscript.core/src/stdlib/pyscript/storage.py

from polyscript import storage as _storage
from pyscript.flatted import parse as _parse
from pyscript.flatted import stringify as _stringify


# convert a Python value into an IndexedDB compatible entry
def _to_idb(value):
    if value is None:
        return _stringify(["null", 0])
    if isinstance(value, (bool, float, int, str, list, dict, tuple)):
        return _stringify(["generic", value])
    if isinstance(value, bytearray):
        return _stringify(["bytearray", [v for v in value]])
    if isinstance(value, memoryview):
        return _stringify(["memoryview", [v for v in value]])
    raise TypeError(f"Unexpected value: {value}")


# convert an IndexedDB compatible entry into a Python value
def _from_idb(value):
    (
        kind,
        result,
    ) = _parse(value)
    if kind == "null":
        return None
    if kind == "generic":
        return result
    if kind == "bytearray":
        return bytearray(result)
    if kind == "memoryview":
        return memoryview(bytearray(result))
    return value


class Storage(dict):
    def __init__(self, store):
        super().__init__({k: _from_idb(v) for k, v in store.entries()})
        self.__store__ = store

    def __delitem__(self, attr):
        self.__store__.delete(attr)
        super().__delitem__(attr)

    def __setitem__(self, attr, value):
        self.__store__.set(attr, _to_idb(value))
        super().__setitem__(attr, value)

    def clear(self):
        self.__store__.clear()
        super().clear()

    async def sync(self):
        await self.__store__.sync()


async def storage(name="", storage_class=Storage):
    if not name:
        raise ValueError("The storage name must be defined")
    return storage_class(await _storage(f"@pyscript/{name}"))



File: pyscript.core/src/stdlib/pyscript/workers.py

import js as _js
from polyscript import workers as _workers

_get = _js.Reflect.get


def _set(script, name, value=""):
    script.setAttribute(name, value)


# this solves an inconsistency between Pyodide and MicroPython
# @see https://github.com/pyscript/pyscript/issues/2106
class _ReadOnlyProxy:
    def __getitem__(self, name):
        return _get(_workers, name)

    def __getattr__(self, name):
        return _get(_workers, name)


workers = _ReadOnlyProxy()


async def create_named_worker(src="", name="", config=None, type="py"):
    from json import dumps

    if not src:
        raise ValueError("Named workers require src")

    if not name:
        raise ValueError("Named workers require a name")

    s = _js.document.createElement("script")
    s.type = type
    s.src = src
    _set(s, "worker")
    _set(s, "name", name)

    if config:
        _set(s, "config", isinstance(config, str) and config or dumps(config))

    _js.document.body.append(s)
    return await workers[name]



File: pyscript.core/src/stdlib/pyscript/ffi.py

try:
    import js
    from pyodide.ffi import create_proxy as _cp
    from pyodide.ffi import to_js as _py_tjs

    from_entries = js.Object.fromEntries

    def _tjs(value, **kw):
        if not hasattr(kw, "dict_converter"):
            kw["dict_converter"] = from_entries
        return _py_tjs(value, **kw)

except:
    from jsffi import create_proxy as _cp
    from jsffi import to_js as _tjs

create_proxy = _cp
to_js = _tjs



File: pyscript.core/src/stdlib/pyscript/websocket.py

import js
from pyscript.ffi import create_proxy
from pyscript.util import as_bytearray

code = "code"
protocols = "protocols"
reason = "reason"


class EventMessage:
    def __init__(self, event):
        self._event = event

    def __getattr__(self, attr):
        value = getattr(self._event, attr)

        if attr == "data" and not isinstance(value, str):
            if hasattr(value, "to_py"):
                return value.to_py()
            # shims in MicroPython
            return memoryview(as_bytearray(value))

        return value


class WebSocket(object):
    CONNECTING = 0
    OPEN = 1
    CLOSING = 2
    CLOSED = 3

    def __init__(self, **kw):
        url = kw["url"]
        if protocols in kw:
            socket = js.WebSocket.new(url, kw[protocols])
        else:
            socket = js.WebSocket.new(url)
        object.__setattr__(self, "_ws", socket)

        for t in ["onclose", "onerror", "onmessage", "onopen"]:
            if t in kw:
                # Pyodide fails at setting socket[t] directly
                setattr(socket, t, create_proxy(kw[t]))

    def __getattr__(self, attr):
        return getattr(self._ws, attr)

    def __setattr__(self, attr, value):
        if attr == "onmessage":
            self._ws[attr] = lambda e: value(EventMessage(e))
        else:
            self._ws[attr] = value

    def close(self, **kw):
        if code in kw and reason in kw:
            self._ws.close(kw[code], kw[reason])
        elif code in kw:
            self._ws.close(kw[code])
        else:
            self._ws.close()

    def send(self, data):
        if isinstance(data, str):
            self._ws.send(data)
        else:
            buffer = js.Uint8Array.new(len(data))
            for pos, b in enumerate(data):
                buffer[pos] = b
            self._ws.send(buffer)



# PyScript

## What is PyScript

### Summary

PyScript is a framework that allows users to create rich Python applications in the browser using HTML's interface and the power of [Pyodide](https://pyodide.org/en/stable/), [MicroPython](https://micropython.org/) and [WASM](https://webassembly.org/), and modern web technologies.

To get started see the [Beginning PyScript tutorial](https://docs.pyscript.net/latest/beginning-pyscript/).

For examples see [here](https://pyscript.com/@examples).

Other useful resources:

-   The [official technical docs](https://docs.pyscript.net/).
-   Our current [Home Page](https://pyscript.net/) on the web.
-   A free-to-use [online editor](https://pyscript.com/) for trying PyScript.
-   Our community [Discord Channel](https://discord.gg/BYB2kvyFwm), to keep in touch .

Every Tuesday at 15:30 UTC there is the _PyScript Community Call_ on zoom, where we can talk about PyScript development in the open. Most of the maintainers regularly participate in the call, and everybody is welcome to join.

Every other Thursday at 16:00 UTC there is the _PyScript FUN_ call: this is a call in which everybody is encouraged to show what they did with PyScript.

For more details on how to join the calls and up to date schedule, consult the official calendar:

-   [Google calendar](https://calendar.google.com/calendar/u/0/embed?src=d3afdd81f9c132a8c8f3290f5cc5966adebdf61017fca784eef0f6be9fd519e0@group.calendar.google.com&ctz=UTC) in UTC time;
-   [iCal format](https://calendar.google.com/calendar/ical/d3afdd81f9c132a8c8f3290f5cc5966adebdf61017fca784eef0f6be9fd519e0%40group.calendar.google.com/public/basic.ics).

### Longer Version

PyScript is a meta project that aims to combine multiple open technologies into a framework that allows users to create sophisticated browser applications with Python. It integrates seamlessly with the way the DOM works in the browser and allows users to add Python logic in a way that feels natural both to web and Python developers.

## Try PyScript

To try PyScript, import the appropriate pyscript files into the `<head>` tag of your html page:

```html
<head>
    <link
        rel="stylesheet"
        href="https://pyscript.net/releases/2024.6.2/core.css"
    />
    <script
        type="module"
        src="https://pyscript.net/releases/2024.6.2/core.js"
    ></script>
</head>
<body>
    <script type="py" terminal>
        from pyscript import display
        display("Hello World!") # this goes to the DOM
        print("Hello terminal") # this goes to the terminal
    </script>
</body>
```

You can then use PyScript components in your html page. PyScript currently offers various ways of running Python code:

-   `<script type="py">`: can be used to define python code that is executable within the web page.
-   `<script type="py" src="hello.py">`: same as above, but the python source is fetched from the given URL.
-   `<script type="py" terminal>`: same as above, but also creates a terminal where to display stdout and stderr (e.g., the output of `print()`); `input()` does not work.
-   `<script type="py" terminal worker>`: run Python inside a web worker: the terminal is fully functional and `input()` works.
-   `<py-script>`: same as `<script type="py">`, but it is not recommended because if the code contains HTML tags, they could be parsed wrongly.
-   `<script type="mpy">`: same as above but use MicroPython instead of Python.

Check out the [official docs](https://docs.pyscript.net/) for more detailed documentation.

## How to Contribute

Read the [contributing guide](https://docs.pyscript.net/latest/contributing/) to learn about our development process, reporting bugs and improvements, creating issues and asking questions.

Check out the [developing process](https://docs.pyscript.net/latest/developers/) documentation for more information on how to setup your development environment.

## Governance

The [PyScript organization governance](https://github.com/pyscript/governance) is documented in a separate repository.

## Release

To cut a new release of PyScript simply
[add a new release](https://github.com/pyscript/pyscript/releases) while
remembering to write a comprehensive changelog. A [GitHub action](https://github.com/pyscript/pyscript/blob/main/.github/workflows/publish-release.yml)
will kick in and ensure the release is described and deployed to a URL with the
pattern: https://pyscript.net/releases/YYYY.M.v/ (year/month/version - as per
our [CalVer](https://calver.org/) versioning scheme).

Then, the following three separate repositories need updating:

-   [Documentation](https://github.com/pyscript/docs) - Change the `version.json`
    file in the root of the directory and then `node version-update.js`.
-   [Homepage](https://github.com/pyscript/pyscript.net) - Ensure the version
    referenced in `index.html` is the latest version.
-   [PSDC](https://pyscript.com) - Use discord or Anaconda Slack (if you work at
    Anaconda) to let the PSDC team know there's a new version, so they can update
    their project templates.
