From 4bf63a87659780508875491f1e7d3d18fbdc0d47 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Thu, 13 Feb 2025 16:40:51 +0200 Subject: [PATCH 01/14] Rename StyleObject to StyleSheet --- .../standard/transformers/style_transformer.py | 6 +++--- src/pydom/styling/__init__.py | 4 ++-- src/pydom/styling/{style_object.py => stylesheet.py} | 12 ++++++------ src/pydom/types/html/html_element.py | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) rename src/pydom/styling/{style_object.py => stylesheet.py} (78%) diff --git a/src/pydom/context/standard/transformers/style_transformer.py b/src/pydom/context/standard/transformers/style_transformer.py index e5dac3d..c39c126 100644 --- a/src/pydom/context/standard/transformers/style_transformer.py +++ b/src/pydom/context/standard/transformers/style_transformer.py @@ -1,10 +1,10 @@ -from ....styling import StyleObject +from ....styling import StyleSheet from ...transformers import PropertyTransformer class StyleTransformer(PropertyTransformer): def match(self, _, value): - return isinstance(value, StyleObject) + return isinstance(value, StyleSheet) - def transform(self, key: str, value: StyleObject, element): + def transform(self, key: str, value: StyleSheet, element): element.props[key] = value.to_css() diff --git a/src/pydom/styling/__init__.py b/src/pydom/styling/__init__.py index c4cfdec..c442839 100644 --- a/src/pydom/styling/__init__.py +++ b/src/pydom/styling/__init__.py @@ -1,5 +1,5 @@ from .color import Color -from .style_object import StyleObject +from .stylesheet import StyleSheet from .css_modules import CSS -__all__ = ["Color", "StyleObject", "CSS"] \ No newline at end of file +__all__ = ["Color", "StyleSheet", "CSS"] \ No newline at end of file diff --git a/src/pydom/styling/style_object.py b/src/pydom/styling/stylesheet.py similarity index 78% rename from src/pydom/styling/style_object.py rename to src/pydom/styling/stylesheet.py index 1c1b22a..882c534 100644 --- a/src/pydom/styling/style_object.py +++ b/src/pydom/styling/stylesheet.py @@ -6,9 +6,9 @@ T = TypeVar("T") -class StyleObject: +class StyleSheet: class _StyleProperty(Generic[T]): - def __init__(self, instance: "StyleObject", name: str): + def __init__(self, instance: "StyleSheet", name: str): self.instance = instance self.name = name.replace("_", "-") @@ -18,12 +18,12 @@ def __call__(self, value: T): def __init__( self, - *styles: Union["StyleObject", CSSProperties], + *styles: Union["StyleSheet", CSSProperties], **kwargs: Unpack[CSSProperties], ): self.style: Dict[str, object] = {} for style in styles: - if isinstance(style, StyleObject): + if isinstance(style, StyleSheet): style = style.style self.style.update(style) self.style.update(kwargs) @@ -32,7 +32,7 @@ def __init__( } def copy(self): - return StyleObject(self) + return StyleSheet(self) def to_css(self): return "".join(map(lambda x: f"{x[0]}:{x[1]};", self.style.items())) @@ -41,4 +41,4 @@ def __str__(self): return self.to_css() def __getattr__(self, name: str): - return StyleObject._StyleProperty(self, name) + return StyleSheet._StyleProperty(self, name) diff --git a/src/pydom/types/html/html_element.py b/src/pydom/types/html/html_element.py index d73bbdc..e7ce527 100644 --- a/src/pydom/types/html/html_element.py +++ b/src/pydom/types/html/html_element.py @@ -3,7 +3,7 @@ from typing_extensions import TypedDict if TYPE_CHECKING: - from pydom.styling import StyleObject + from pydom.styling import StyleSheet class HTMLElement(TypedDict, total=False, closed=False): @@ -21,7 +21,7 @@ class HTMLElement(TypedDict, total=False, closed=False): lang: Optional[str] role: Optional[str] spell_check: Optional[str] - style: Optional[Union[str, "StyleObject"]] + style: Optional[Union[str, "StyleSheet"]] tab_index: Optional[str] title: Optional[str] translate: Optional[str] From bbe400155b572fa8fe2f9f898c591331b757fd44 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Thu, 13 Feb 2025 21:16:49 +0200 Subject: [PATCH 02/14] Refactor CSSModule to use get_frame utility for improved frame retrieval --- src/pydom/styling/css_modules.py | 17 +++++++---------- src/pydom/utils/get_frame.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 src/pydom/utils/get_frame.py diff --git a/src/pydom/styling/css_modules.py b/src/pydom/styling/css_modules.py index 818223a..49307be 100644 --- a/src/pydom/styling/css_modules.py +++ b/src/pydom/styling/css_modules.py @@ -1,4 +1,3 @@ -import inspect import re from os import PathLike @@ -7,6 +6,8 @@ import cssutils +from ..utils.get_frame import get_frame + from ..utils.functions import random_string @@ -79,7 +80,7 @@ def __init__(self, module_name): if rule.type == rule.UNKNOWN_RULE: self.raw_css += rule.cssText - def __getattr__(self, __name: str): + def __getattr__(self, __name: str) -> str: if __name not in self.classes: raise AttributeError(f"CSS class {__name} not found in {self.module_name}") return self.classes[__name].uuid @@ -139,14 +140,10 @@ def set_root_folder(cls, folder: Union[PathLike, str]): def _full_path(cls, css_path: Union[PathLike, str]) -> Path: if isinstance(css_path, str): if css_path.startswith("./"): - frame = inspect.stack()[2] - module = inspect.getmodule(frame[0]) - if module is None or module.__file__ is None: - raise ValueError( - "Cannot use relative path in a module without a file" - ) - - css_path = Path(module.__file__).parent / css_path + frame = get_frame(2) + module = frame.f_globals["__name__"] + module_path = Path(module.replace(".", "/")) + css_path = module_path.parent / css_path[2:] css_path = Path(css_path) diff --git a/src/pydom/utils/get_frame.py b/src/pydom/utils/get_frame.py new file mode 100644 index 0000000..3809480 --- /dev/null +++ b/src/pydom/utils/get_frame.py @@ -0,0 +1,23 @@ +import sys +from sys import exc_info +from types import FrameType + + +def get_frame_fallback(n: int): + try: + raise Exception + except Exception: + frame: FrameType = exc_info()[2].tb_frame.f_back # type: ignore + for _ in range(n): + frame = frame.f_back # type: ignore + return frame + + +def load_get_frame_function(): + if hasattr(sys, "_getframe"): + return sys._getframe + + return get_frame_fallback + + +get_frame = load_get_frame_function() From f5f4d01aa78cc6dc0a7ec02c45a25934266fd92b Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Thu, 13 Feb 2025 21:17:04 +0200 Subject: [PATCH 03/14] Add tests for CSS modules --- tests/css/styles.css | 13 +++++++++++++ tests/test_css_modules.py | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/css/styles.css create mode 100644 tests/test_css_modules.py diff --git a/tests/css/styles.css b/tests/css/styles.css new file mode 100644 index 0000000..a8082fe --- /dev/null +++ b/tests/css/styles.css @@ -0,0 +1,13 @@ +.card { + background-color: #fff; + border-radius: 5px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin: 10px; + padding: 20px; +} + +.cardHeader { + font-size: 1.5em; + font-weight: bold; + margin-bottom: 10px; +} \ No newline at end of file diff --git a/tests/test_css_modules.py b/tests/test_css_modules.py new file mode 100644 index 0000000..30c8d8f --- /dev/null +++ b/tests/test_css_modules.py @@ -0,0 +1,21 @@ +from pydom.styling import CSS + +from .base import TestCase + + +class CSSModulesTest(TestCase): + @classmethod + def setUpClass(cls) -> None: + styles = CSS.module("tests/css/styles.css") + cls.card_class = styles.card + cls.card_header_class = styles.cardHeader + + def test_class_name(self): + styles = CSS.module("tests/css/styles.css") + self.assertEqual(self.card_class, styles.card) + self.assertEqual(self.card_header_class, styles.cardHeader) + + def test_relative_css(self): + styles = CSS.module("./css/styles.css") + self.assertEqual(styles.card, self.card_class) + self.assertEqual(styles.cardHeader, self.card_header_class) From 6bb8c8b31364d50d7928406ce4f7696dfe166fa9 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Thu, 13 Feb 2025 21:17:42 +0200 Subject: [PATCH 04/14] Rename class_name to classes in ClassTransformer and related components --- .../context/standard/transformers/class_transformer.py | 7 +++++-- src/pydom/types/element.py | 2 +- src/pydom/types/html/html_element.py | 2 +- src/pydom/types/svg/svg_element_props.py | 2 +- tests/components/__init__.py | 8 ++++---- tests/simple.py | 2 +- tests/test_json_rendering.py | 4 ++-- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/pydom/context/standard/transformers/class_transformer.py b/src/pydom/context/standard/transformers/class_transformer.py index 67aaf78..eaa7c12 100644 --- a/src/pydom/context/standard/transformers/class_transformer.py +++ b/src/pydom/context/standard/transformers/class_transformer.py @@ -2,12 +2,15 @@ class ClassTransformer(PropertyTransformer): + def __init__(self, prop_name="classes"): + self.prop_name = prop_name + def match(self, prop_name, _) -> bool: - return prop_name == "class_name" + return prop_name == self.prop_name def transform(self, _, prop_value, element): if not isinstance(prop_value, str): prop_value = " ".join(prop_value) element.props["class"] = " ".join(str(prop_value).split()).strip() - del element.props["class_name"] + del element.props[self.prop_name] diff --git a/src/pydom/types/element.py b/src/pydom/types/element.py index be5a014..575abec 100644 --- a/src/pydom/types/element.py +++ b/src/pydom/types/element.py @@ -4,7 +4,7 @@ class ElementProps(TypedDict, total=False, closed=False): access_key: str auto_capitalize: str - class_name: str + classes: str content_editable: str dangerously_set_inner_html: str dir: str diff --git a/src/pydom/types/html/html_element.py b/src/pydom/types/html/html_element.py index e7ce527..8d179d8 100644 --- a/src/pydom/types/html/html_element.py +++ b/src/pydom/types/html/html_element.py @@ -9,7 +9,7 @@ class HTMLElement(TypedDict, total=False, closed=False): access_key: Optional[str] auto_capitalize: Optional[str] - class_name: Optional[Union[str, Iterable[str]]] + classes: Optional[Union[str, Iterable[str]]] content_editable: Optional[str] dangerously_set_inner_html: Optional[Dict[Literal["__html"], str]] # data: Dict[str, str] # add this if needed in the future diff --git a/src/pydom/types/svg/svg_element_props.py b/src/pydom/types/svg/svg_element_props.py index f53862c..acb4e3d 100644 --- a/src/pydom/types/svg/svg_element_props.py +++ b/src/pydom/types/svg/svg_element_props.py @@ -6,7 +6,7 @@ class SVGElementProps(TypedDict, total=False): class _SVGElementProps(TypedDict, total=False): # Attributes also defined in HTMLAttributes - class_name: Optional[str] + classes: Optional[str] color: Optional[str] height: Optional[Union[int, str]] id: Optional[str] diff --git a/tests/components/__init__.py b/tests/components/__init__.py index 2aba954..d0e88bc 100644 --- a/tests/components/__init__.py +++ b/tests/components/__init__.py @@ -9,7 +9,7 @@ def __init__(self, name, version) -> None: def render(self): return Div( - class_name="plugin", + classes="plugin", )( f"{self.name} v{self.version}", ) @@ -21,7 +21,7 @@ def __init__(self, plugins=None) -> None: def render(self): return Div( - class_name="plugin-list", + classes="plugin-list", )( *[Plugin(plugin.name, plugin.version) for plugin in self.plugins], ) @@ -30,7 +30,7 @@ def render(self): class Card(Component): def render(self): return Div( - class_name="card", + classes="card", )( *self.children, ) @@ -39,7 +39,7 @@ def render(self): class CardTitle(Component): def render(self): return H3( - class_name="card-title", + classes="card-title", )( *self.children, ) diff --git a/tests/simple.py b/tests/simple.py index 4293131..c9e7b6d 100644 --- a/tests/simple.py +++ b/tests/simple.py @@ -6,7 +6,7 @@ def index(): return Page(title="Hello, world!")( - Div(class_name=["a", "b"])("Hello, world!") + Div(classes=["a", "b"])("Hello, world!") ) diff --git a/tests/test_json_rendering.py b/tests/test_json_rendering.py index 8b4bf72..72d4f92 100644 --- a/tests/test_json_rendering.py +++ b/tests/test_json_rendering.py @@ -83,14 +83,14 @@ def render(self): "Hello", Div(), id="my-id", - class_name="my-class", + classes="my-class", ) class MyComponent2(Component): def render(self): return Div( MyComponent(), - class_name="my-class", + classes="my-class", ) self.assertRenderJson( From 933c74e1362641d48ee2b39b2abb4048886891a8 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Thu, 13 Feb 2025 23:45:30 +0200 Subject: [PATCH 05/14] Update docstrings for transformer decorators --- .../rendering/transformers/post_render_transformer.py | 6 ++---- src/pydom/rendering/transformers/property_transformer.py | 8 ++------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/pydom/rendering/transformers/post_render_transformer.py b/src/pydom/rendering/transformers/post_render_transformer.py index 636c14d..b8c7c4b 100644 --- a/src/pydom/rendering/transformers/post_render_transformer.py +++ b/src/pydom/rendering/transformers/post_render_transformer.py @@ -5,12 +5,10 @@ def post_render_transformer(context: Union[Context, None] = None): """ - A decorator to register a post-render transformer. - - Post-render transformers are functions that take the rendered tree and can modify it in place. + A decorator to register a function as a post-render transformer. Args: - context: The context to register the transformer with. + context: The context to register the transformer with. If not provided, the default context is used. Returns: A decorator that takes a transformer function and registers it. diff --git a/src/pydom/rendering/transformers/property_transformer.py b/src/pydom/rendering/transformers/property_transformer.py index 5cf5213..620879d 100644 --- a/src/pydom/rendering/transformers/property_transformer.py +++ b/src/pydom/rendering/transformers/property_transformer.py @@ -6,17 +6,13 @@ def property_transformer( matcher: Union[Callable[[str, Any], bool], str], context: Optional[Context] = None ): """ - A decorator to register a property transformer. - - Transformers are functions that take a key, a value, and the element node object. - - After handling the key and value, the transformer should update the element node - properties in place. + A decorator to register a function as a property transformer. Args: matcher: A callable that takes a key and a value and returns a boolean indicating whether the transformer should be applied. If a string is provided, it is assumed to be a key that should be matched exactly. + context: The context to register the transformer in. If not provided, the default context is used. Returns: A decorator that takes a transformer function and registers it. From 1420120a70a5ad612c63a3bc476a43a683a7ee95 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Fri, 14 Feb 2025 14:44:15 +0200 Subject: [PATCH 06/14] Enhance add_post_render_transformer method to accept optional before and after parameters --- src/pydom/context/context.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pydom/context/context.py b/src/pydom/context/context.py index 3946ba2..c9be61c 100644 --- a/src/pydom/context/context.py +++ b/src/pydom/context/context.py @@ -105,13 +105,18 @@ def add_prop_transformer( self._prop_transformers.insert(index, (matcher, self.inject(transformer))) def add_post_render_transformer( - self, transformer: Union[PostRenderTransformerFunction, PostRenderTransformer] + self, + transformer: Union[PostRenderTransformerFunction, PostRenderTransformer], + /, + *, + before: Optional[List[Type[PostRenderTransformer]]] = None, + after: Optional[List[Type[PostRenderTransformer]]] = None, ): try: index = self._find_transformer_insertion_index( self._post_render_transformers, - before=[PostRenderTransformer], - after=[PostRenderTransformer], + before=before, + after=after, ) except Error as e: raise Error( From 2707f4c636861ed2132beaf357b6cb2ba23cc5a5 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Wed, 26 Feb 2025 15:01:26 +0200 Subject: [PATCH 07/14] Simplify add_rule method by using setdefault for sub_rules --- src/pydom/styling/css_modules.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pydom/styling/css_modules.py b/src/pydom/styling/css_modules.py index 49307be..8c75cf5 100644 --- a/src/pydom/styling/css_modules.py +++ b/src/pydom/styling/css_modules.py @@ -18,10 +18,7 @@ def __init__(self, class_name: str): self.uuid = random_string() def add_rule(self, rule: str, properties: Dict[str, str]): - if rule not in self.sub_rules: - self.sub_rules[rule] = {} - - self.sub_rules[rule].update(properties) + self.sub_rules.setdefault(rule, {}).update(properties) def to_css_string(self, minified=False): rules = [] From 4f89b81e0d0e7467e605f6f45f525970cabe8d68 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Wed, 26 Feb 2025 15:06:22 +0200 Subject: [PATCH 08/14] Add Page to module exports in __init__.py --- src/pydom/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pydom/__init__.py b/src/pydom/__init__.py index 85abf54..52ea974 100644 --- a/src/pydom/__init__.py +++ b/src/pydom/__init__.py @@ -1,6 +1,7 @@ from .component import Component from .context.context import Context, set_default_context from .html import * +from .page import Page from .svg import * from .rendering import render from .version import version as __version__ @@ -148,5 +149,6 @@ "Component", "Context", "render", + "Page", "__version__", ] From 1e1f7e5eed065fb72de88e17d49b766b85e1fe3a Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Wed, 26 Feb 2025 15:16:59 +0200 Subject: [PATCH 09/14] Update readme --- README.md | 99 ++++++++++++++++++++++++++- docs/_static/images/logo.svg | 25 +++++++ docs/_static/images/quick-start.jpeg | Bin 0 -> 16178 bytes 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 docs/_static/images/logo.svg create mode 100644 docs/_static/images/quick-start.jpeg diff --git a/README.md b/README.md index 2bb4e46..962a036 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,100 @@ # PyDOM -See [Seamless](https://seamless.rtfd.io) for a more complete and up-to-date implementation of this idea. +

+ pydom-logo +

+ +

+ *Simple to learn, easy to use, fully-featured UI library for Python* +

+ +

+ + PyPI version + +

+ +PyDOM is a Python library that allows you to create web pages using a declarative syntax. + +PyDOM provides a set of components that represent HTML elements and can be composed to create complex web pages. + +## Quick Start + +This is a quick start guide to get you up and running with PyDOM. The guide will show you how to setup PyDOM and integrate it with [FastAPI](https://fastapi.tiangolo.com/). + +### Installation + +First, install the PyDOM package. + +```bash +pip install pydom +``` + +### Create Reusable Page + +PyDOM provides a default page component that is the minimal structure for a web page. + +The page can be customized by extending the default page component and overriding the `head` and the `body` methods. + +More information about the default page component can be found [here](#page). + +```python +# app_page.py + +from pydom import Link, Page + +class AppPage(Page): + def head(self): + return ( + *super().head(), + Link( + rel="stylesheet", + href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" + ) + ) +``` + +### Creating the FastAPI app + +Lastly, create the `FastAPI` app and add an endpoint that will render the page when the user accesses the root route. + +```python +# main.py + +from fastapi import FastAPI +from fastapi.responses import HTMLResponse +from pydom import render, Div, P + +form app_page import AppPage + +app = FastAPI() + +@app.get("/", response_class=HTMLResponse) +async def read_root(): + return render( + AppPage( + Div(classes="container mt-5")( + Div(classes="text-center p-4 rounded")( + Div(classes="display-4")("Hello, World!"), + P(classes="lead")("Welcome to PyDOM"), + ) + ) + ) + ) + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="localhost", port=8000) +``` + +That's it! Now you can run the app and access it at [http://localhost:8000/](http://localhost:8000/). + +It should display a page like this: + +

+ Quick Start +

+ +## Documentation + +The full documentation can be found at [our documentation site](https://pydom.dev/). diff --git a/docs/_static/images/logo.svg b/docs/_static/images/logo.svg new file mode 100644 index 0000000..88bb834 --- /dev/null +++ b/docs/_static/images/logo.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_static/images/quick-start.jpeg b/docs/_static/images/quick-start.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f00888eece21c911a1ed5c8d12cebdee3ad15b4d GIT binary patch literal 16178 zcmeI1c~n!^y1aIM4?A)r9uN;J$M0TKvPt+g@;l&c5? zgcfSR1VRxogoLCj0U1+>Nth$^80HYBkQZ*>TYYb>d*52`pLgFMd}p0~zVq#G?{A-9 zcD}uHBI}lY1de^}aMc0Wvj+h7$PYj^yr=u>l`B5q+<-e=b%Op;@g9)NzKZ}55*mrQ z;rP|ro3}j9Dt`Q<#ZKLARK&fV>pu~BzpMP6-T{DX^iO>LC-cEOcOy`8hXwihIYu5_ zK3G+`uIl%Pett*y{X@6j(Q%RYBIQ2c?C2QR8&~A|9l3tq@4x82|3ydL!|eD!mit(T z1V`<}+L4_RAGsTfaFJgR$j?uK2;c_b2z<5k{PKIbgcSk6WibFK{P?HMHx~e?4*}q0 z@1M5Me*ysI?*ZU7=TF<8Ozz#r-2Qua`{d z|G@)?4jnvj@Zg~%%0~_zRyus};8D;~CFNtsK*tUpIj(a2n2KCKw$sU;ot6sw4#*oG zJACl4-1@&1*>3>ofP&ZlErmVWz+TWE1<)Q@1E3+ty;ot6T>kTE0mTFRKH0xVVedhC z_0?nY`MGb;UIn@7;335wr$25KK?jb1rUL%bSylUX_>f#KYdU3KF_+zQWjD#$$$uhuc>bjph~4d zqt+G;c21U8D)=A0dCPGjS+o7A)dJCMhm9)F-ZO`EK|DV*cn(6UK(f4*m07>zgmphX z^!VwD&DV>bUEBFf`fEt7ESkmVH6V)`k0biaCO7BjR;wIeuo_)jh_`|8iqkeGJ(%F@ zI&G#~PreVvp3vrhx}*_xlW!uhoerqCb74p%ICv-673$jN1TS+s^}{qX+HcG&C)-I#nP_NN}y#LTi0yYJLxF&jJQY&(_w;HV#P{yWB3Q4>!h zuN6kVQ(5wUEjd2!$$B5_MgJfWEgB2@dJy((XD53}ZlNjs#ii%pD5j?u`b=z}p;?U1 zL|x~mLz!19NEuzkIU{d8gB9Gj+%#O(;&V{CPQ&E6P1C(riAe-vg0qs&&;3gRmpW(z zZJ=(7RL&%^X9h|JVp~BNyF;}mw3v%Haqw)-)}4nl&*3qIBw|kd-dr3*uMNw+q8sB zla$iRhxYaZbP&|Jw=`W!jJL6Au0&jx(xwa3rQ;i=t92bY{{DiS1=6X<{TUwPw%sL+ z1Oubyb-2b5-kNf?MRK{W9<8Q=*}e{YUzDOVUzpK-!@Q{0FuV& zmj*V1l1-xXLJ2b9G0`@X zx_y}Crc{{FOx?ajduP=u=66sr#)POVFSc~xWrES+h_n~|pZ3vI1r~;#FfiDBtHvHD zUTYw}KrDKu;dB~I9&!BOlS@Z(a|>}VYO_fC-)evRuTHan#@}18*4K{-QM_rXMIGwD z9%DXoCM?@qmjpTAuz^TuU5PIbH|?AnjO8@*5(`-;?Pl$8+jasO(EWB1y1`yckkYa@ zb3NQDl2SO^30t=lf0YW?WPpzh@YU;yC@7CeJY9dRo)?rD0n!keDJ(A2W~X`U7JOpB@NIyKju<6fFR zu!+8d7%i%pl@M6_-5*hs!?#oK8EPQOk$0CqorxR|!wQ&82W-^)WhA9;f{$v4fV2)I zLG9j}k<&8lSF%P&eVTr&Y>kRS4_0|~BFSc6gg^?Rcm-`GNnV4adjs^j zlrs{HfPBi*scv}FwfbzMxhp}BpI#`c)w}Sf3eQili5w9UP`$oF(K`M0w1kT=!xqAy ze7oSxv0zO6L?FkJwad~3r(2pwdIY3g0>wSmt5eO1(-^QM*}P4Z0bh2UkpcZkFBy=! zbR1`2_gMq}F#A<>_R;2^QQxr%WcMJwvUycPuE*%KLwx+_h15h=UHBu*lsG*ZVBmc= zk&?NYW9_*8N<%?fp0}B7{g2JwJp~#isR^WzI_U3G&SxWu>vM0%(rin(tZW0_S}F|5 z8b~Hp%vQx&t>~q#xIY*g>SY%jWcu}$nBZR5YsNips~c&@kjRDJ1LM9rV+Vw5|29eC zT&w+IdbI6!U<|*|;8MjybB35Zn7}b}EfeLvp6@9VlZr^jE^02|xtyH&}VWLhXze;ag?)q<}kEZgH@+!jcplf0KqSJO`0%HaENdxX(GQ@=a53 z(2_i54v#&in|0Idn_jr`oEjRok2mjei#ve66CQ73HF>>dG+wWSk*#;;baL+eiUALX z^%D&BCXP2RLS4mrWzV%1q!|!zb2Ojn$tbe%z4$9?Ig+=Ez)*zo7ul^vc=wTt6PN7F z$kPSi1-ma0BP#tE2@rVLoL{7paJ_Bu07JOP=2AG6R z=y8y#v4F)5{Fv0z#{B$NS%7h{ICoD0Pe$R)AoVURnM z5zr)3bL`MYpB>~jLJQsqJ_dn}KF<6)JA&#Vo~H)#Ku}c|&B>&UzkHQoP-jM&kXpBo z)m>Bl7-6dySpND+PzL;OQZm1oGfF7t2avRVeO-#b{BUFz=J&~{)sg9!C0bhUS(s7Y z+>$(*XO>v}Zd@$j6{Lr!$0uayUg!EiZ@DI#OTl9_Eg9gfU-Pl5L^$@y6`$R_Zj&10 zK{0AujVNiP!SNT`>ap=Jrjl0(FNrxx>oG4btsQ&U%wAx#=kBnbPeo*9q4m>+1X@B} z>6Vp~>Gu8$L{sSv(uMTp4=`5D8b|POv8=-leZELn_d-(6O7d(u^0YMEk|$MmmjU)= zb=8q!;)WUb6&oMv@ywv1;R?He50qm07VPbK;q63aKpJO+; zgDzjV{{5}cpG(ufR#QuCnA7xjObWg4U73m>qgydv(vYf7T>IEl;@k|}aJ90oHd@zL z7u7G0R12frjKML2BNg@eM$@ym-VCx^pLRl^8Y1UD#%3|Aw>rbhzRjrtml65PkfGoV z-!^AzqnQhn0We7xj1j^==N3>88YG`U@dw^-22d^(zVWt14L|Vvy*$*!k>MU_PC#~~ zB)xqz`uZYebcTBJL%b)>cdQN_P|@0}Kd+^<+#CCL09;kmD}A2Z*!^*2%%d=#&L6Jx zTgc)bQ<`r&@fY31b1OoXAEOi!@m9XmD|k8&)>|?#PDg^Fs#_C`HpjOvjtV_XyHthv0Sm_5Y;&i#Uu7(*rCRIIv zs$$oZgjUoS47sZqXpyAxw9GX5Qc8P@2qjGteCy&L;~O|k+mQx0U4#q(#VfM>PK#Qx z*q$m(oqsMdX;o!DM`D<7?--MmG(Z2JXu~Z1PL5jAxJW(sjVQ*{;I=N<;&RQc)u-E8 zd6_ezV|F2CP>2?W5joRPF!OGkrS(g}h3eRlP`77E4g4jBmjTT~N-@)XHccpJBb*Mb-V*gkm^&hNtE2p;rOFqADVJ{LNe-RzVX3K>iFj7%KE(; z%aFU9SwfoaG`g-d<$U_~_Og)r_Ey2^4VQMa*o3S6xeAXOzr1DqBw@jy%(+h9pD|qN zTk*I7j%R-8tN7i&#th+2hCO(MNPJ-qo}@w~>%OPPbM9}5KNLh%OlLUZ9l0TGL91SQ zs0c_19xNIlm|-u>y_&dPjc<#S+9IDhonte| z9jQ&YNA>BS@>s&)+K$F{yBUuGCRv-esn*2I_*$1h)LXAI)+@ESHL7VGUh<2E0(Z`E zF(j)c1alpg_b5U|ldqQ`?r9>9x*%J#97_^9K`M(LWpidt*lcFztKVmq<{Gfe!p5RH zdi?T{8FfRH2eHG3VB|@(a)GL{!Jt+9Gb(1};d~V0?HZGsJUI)^KYINf!wIi51XXQt zTsv#B?{e-li1+gW! zUQtlUqV|5+9ZwoUMU~f^Jl`AN^$Kof506foKYvzP?fK{9aYdJ@?i;W`UI&iLXsNmw zK2vBI3=T?4U8}OQEV8j3TwJ^z9;Q)$yhE^QjLWJX^4#)>qiw{$el4{KM#_It6N-|F zqc{n+KnC0~3_?C{ir(&8xf5I+eF_(_k}^NyjC5XWGbZXM-rD$?xd`QoWPtJ{pB(k@ z-TB{GiMW=f1#)AF_COsvA^B10C=_QvA;ltGxggN;+_ShdC?R{H{WtJZuvZl}4FN)& zxIhRb#&VeFF7?#}@@i`W2TYxvuqgrQogj&wGYHhHQ+=lKRL9SuucNaYCpq6l!xid( zv+!&l|3U>dRT}{cJ8u1fJBVtv4VZU@hb~U3*l?ZT(aGzq6X3;pcWCQtBLR{&4@J1B zm~wiS;z9rRuU!)ULG=9@d3WdTT`u6-Wgwh_!Qo^UTEEC(83r0B#KJqYQH$3wfz8x2 zZ%&C-NN1Gneyi>;Oh-9C$*!WKbVHnC=(qcdFqm;|Tidt1-T)bpK(Y;e6w&-@ax_2I zh5WwiKNUt^9!n-KMz7N zIyyj1jdZq$x$7@+L8@F8NMAqN#B*KP)0d7GIIm^lt@2amys^d_^7ju=Q7*Zqk%jC@ z5<%6C`-r0ELR0w%Ua&nmw-G>-0cwLI<34XXSfTff+Gq2=$gxeXvanVm|O9QV4Igo$U*rLKK>E?7RS%`i2(2c)`AG!&AOsQ6NOCUF};Af~{5 z40uREYJPYDG&MW7qJtjz_&{z;iPiB{wN};D`Ekm?#(4@M7Qarka*DfVxRiHhsBOwJ ztszFKb;+U1PH)PLjkxvrd4hx4DbpquP?p;rE8~N7@pSOL~+Ap;2cpqV9TO1B^u+`Y= zPy4=~Q*&Z=xSMxZ*qWEfA)g+ot8fMlYNJUy)f;efNU<>R3teU!G21*pKX96qVd`Sa zgGF)zOG$(x{i4#G9714HPL2q?cjm7;<>eh=@4wgb ztnln8f8DrX4xtVS^?p~AmZ+YW6%#;ne3e8b1>|O!I^jffS^3$?FHhccf1OVy%wav| zE--3}`GF?V<8x7$SMGSfZ!W3sFCqlZLrq~~fc}H8@c#po7GWx7ij-2MtDl^lobX*zB5}j8Z!0_Gr@o-M&Wd66OQzjwNMc-e zX*2Bvx-?g6%nxWPVZ9Bc*oHWyreTotN@E00@fd0%K%5mG5+B6{VMKHY#LUI4f$iOu zad!s1!#~kgw5B;yw#wNQTL~i#8pEsmQ?0I1SUk zp{`sNjgp%1K|N~OwW*5Ly2Ej(Cv)IecAw8g;8gEA-6B;K3vk*jKP<`DYCYDzDPgd{ zf1T)0%qduOvX?eAU`4%F#uFe2+NZ;0;8fVUd0|M+h}SX_GneqdIs1v9zKDSz2hX{q zTICyI!zO}nf!G-WniA=a8OAdO%6W4b3x=EV=*PeMo1a`i71>Kk3GZd$pn7wNt% zX?ESLw-yE>q0=_Ob5MR<}U0dTAf!J5wkGu~uv>#u>Rx5d8_$R23(v zGgw^Az+M~pftl!;7^Am=T9ImT=^{N5E7FmDz6*2x3b`Kcy6!E}^Tc3u6ip7Rx2p#& zwK;j^MZca-9~jrQ9+bH72*jonP2Gsly0VX|eg+z4^%eRNK zd)rOhuuDz;Ygn7XAM0vUt1j!(v!Z-rvR>CSD+BrTLwOPRYun#YBls_KNQPcTd4*BP zZKv&@KL)KiYJ9*vH~xg-<$VfoBGqJrVBm)cNT{?{{*MIaB}r%f!;K%A0!UkQhcK)E z9dFx(UHLPq!knWSK26$obwQZ9gyt1Q6I1l6+0Ld2h{}{{<FDp~zL6Rd`pPySJRdu-C|tDbg~nBGI&&i9w$ zM@Bwvz4FWZ*48#Yx48WsF*$`e7R6h!s&Qm$r}Em>;BWJ|t{dOX*0N5MA4k}rUnSEL z48vzC6ZQRbQhU@!JCQBp@tp`nI|93TL(5+@r|t!wJW~4qAbIT$VVA%zfn5T-1a=AR z64)iMOJJA4E`ePFy99O#>=M`|uuEW}5d1QcXF F{|<=tPpkj{ literal 0 HcmV?d00001 From a532789be016c84e424142165407272abf6a7962 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Wed, 26 Feb 2025 15:18:37 +0200 Subject: [PATCH 10/14] Update image links in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 962a036..b33cee0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PyDOM

- pydom-logo + pydom-logo

@@ -92,7 +92,7 @@ That's it! Now you can run the app and access it at [http://localhost:8000/](htt It should display a page like this:

- Quick Start + Quick Start

## Documentation From f175fa40bc841a315f33da9a716ef61c2ab3a265 Mon Sep 17 00:00:00 2001 From: Neriya Cohen <42520786+neriyaco@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:21:35 +0200 Subject: [PATCH 11/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b33cee0..b6f8b43 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

- *Simple to learn, easy to use, fully-featured UI library for Python* + Simple to learn, easy to use, fully-featured UI library for Python

From 26b0aa324729cd79540b2bab5fa13f1db25ebc76 Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Wed, 26 Feb 2025 15:31:11 +0200 Subject: [PATCH 12/14] Rename package from python-dom to pydom and update project metadata in pyproject.toml --- pyproject.toml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 16f8cd0..b65f5c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,22 @@ [project] -name = "python-dom" +name = "pydom" authors = [{ name = "Xpo Development", email = "dev@xpo.dev" }] description = "A Python package for creating and manipulating reusable HTML components" readme = "README.md" requires-python = ">=3.8" classifiers = [ - "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", + "Environment :: Web Environment", + "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dynamic = ["dependencies", "version"] @@ -28,3 +37,6 @@ dependencies = { file = ["requirements.txt"] } [tool.setuptools.package-data] pydom = ["py.typed"] + +[project.scripts] +pydom = "pydom.cli:main" From a23e30968fd548edb8ee0fa91a1d8ba98f2231db Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Wed, 26 Feb 2025 15:32:02 +0200 Subject: [PATCH 13/14] Update image links in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b6f8b43..5732d17 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PyDOM

- pydom-logo + pydom-logo

@@ -92,7 +92,7 @@ That's it! Now you can run the app and access it at [http://localhost:8000/](htt It should display a page like this:

- Quick Start + Quick Start

## Documentation From 5e591d932fec0b19ed307c9372f8e1f34852d81f Mon Sep 17 00:00:00 2001 From: Neriya Cohen Date: Wed, 26 Feb 2025 15:47:11 +0200 Subject: [PATCH 14/14] Add remove_prefix function to support python 3.8 --- src/pydom/styling/css_modules.py | 4 ++-- src/pydom/utils/functions.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pydom/styling/css_modules.py b/src/pydom/styling/css_modules.py index 8c75cf5..77ace1c 100644 --- a/src/pydom/styling/css_modules.py +++ b/src/pydom/styling/css_modules.py @@ -8,7 +8,7 @@ from ..utils.get_frame import get_frame -from ..utils.functions import random_string +from ..utils.functions import random_string, remove_prefix class CSSClass: @@ -65,7 +65,7 @@ def __init__(self, module_name): css_property.name: css_property.value for css_property in rule.style } - base_name = first_selector.seq[0].value.removeprefix(".") + base_name = remove_prefix(first_selector.seq[0].value, ".") css_class = self.classes.get(base_name, CSSClass(base_name)) for selector in selectors: css_class.add_rule( diff --git a/src/pydom/utils/functions.py b/src/pydom/utils/functions.py index f58f25c..e839a43 100644 --- a/src/pydom/utils/functions.py +++ b/src/pydom/utils/functions.py @@ -31,3 +31,11 @@ def flatten(iterable): yield from flatten(item) else: yield item + + +def remove_prefix(text: str, prefix: str) -> str: + if hasattr(str, "removeprefix"): + return text.removeprefix(prefix) + if text.startswith(prefix): + return text[len(prefix) :] + return text