hypie 0.1.1.dev4__tar.gz → 0.1.1.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. hypie-0.1.1.dev5/PKG-INFO +104 -0
  2. hypie-0.1.1.dev5/README.md +90 -0
  3. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/pyproject.toml +2 -1
  4. hypie-0.1.1.dev5/src/hypie/__init__.py +6 -0
  5. hypie-0.1.1.dev5/src/hypie/cli/build_components.py +142 -0
  6. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/commands.py +21 -27
  7. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/events.py +77 -77
  8. hypie-0.1.1.dev5/src/hypie/experimental/client_component.py +120 -0
  9. hypie-0.1.1.dev5/src/hypie/experimental/server_component.py +56 -0
  10. hypie-0.1.1.dev5/src/hypie/experimental/style.py +62 -0
  11. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/features.py +37 -0
  12. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/hs_ast/commands.py +35 -2
  13. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/hs_ast/expressions.py +67 -35
  14. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/hs_ast/features.py +73 -3
  15. hypie-0.1.1.dev5/src/hypie/hs_ast/helpers.py +120 -0
  16. hypie-0.1.1.dev4/PKG-INFO +0 -915
  17. hypie-0.1.1.dev4/README.md +0 -901
  18. hypie-0.1.1.dev4/src/hypie/__init__.py +0 -34
  19. hypie-0.1.1.dev4/src/hypie/cli/build_components.py +0 -148
  20. hypie-0.1.1.dev4/src/hypie/experimental/client_components.py +0 -78
  21. hypie-0.1.1.dev4/src/hypie/experimental/components.py +0 -135
  22. hypie-0.1.1.dev4/src/hypie/experimental/hyperscript.py +0 -35
  23. hypie-0.1.1.dev4/src/hypie/experimental/templates.py +0 -50
  24. hypie-0.1.1.dev4/src/hypie/hs_ast/helpers.py +0 -85
  25. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/LICENSE +0 -0
  26. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/dom_position.py +0 -0
  27. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/hs_ast/constants.py +0 -0
  28. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/hs_ast/contexts.py +0 -0
  29. {hypie-0.1.1.dev4 → hypie-0.1.1.dev5}/src/hypie/literals.py +0 -0
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: hypie
3
+ Version: 0.1.1.dev5
4
+ Summary: Add your description here
5
+ Author: Yasser Ahmed
6
+ Author-email: Yasser Ahmed <yasserfawzi1029@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Dist: htpy>=26.5.1
10
+ Requires-Dist: watchfiles>=1.2.0
11
+ Requires-Python: >=3.12
12
+ Project-URL: Repository, https://github.com/Infrared1029/hypie
13
+ Description-Content-Type: text/markdown
14
+
15
+ <p align="center">
16
+ <img src="https://raw.githubusercontent.com/Infrared1029/hypie/main/assets/hypie_logo.jpg" alt="Logo" width="400">
17
+ </p>
18
+
19
+ ## Intro
20
+ Hypie, pronounced "high-pie", is a python library for building web frontends, built on top of [_hyperscript](https://github.com/bigskysoftware/_hyperscript), a scripting language for the web made by the [htmx](https://github.com/bigskysoftware/htmx) folks, and [htpy](https://github.com/pelme/htpy), a library for generating HTML in python.
21
+
22
+ The goal of hypie is to be able to code powerful web frontends, server-side rendered or client-side rendered (or a mix of both), without having to leave python, or switch your favorite backend, using relatively little abstractions and no reactivity.
23
+
24
+ New README in the process, for now enjoy this counter example:
25
+ ```python
26
+ import htpy
27
+ import hypie as hp
28
+
29
+
30
+ class CounterWidget(hp.ServerComponent):
31
+ initial_count: int = 0
32
+
33
+ def template(self):
34
+ count_var = hp.var(":count")
35
+
36
+ return htpy.div[
37
+ htpy.button(
38
+ _=hp.hs(
39
+ hp.set_(count_var, to=self.initial_count),
40
+ hp.On("click")[
41
+ hp.set_(count_var, to=count_var + 1),
42
+ hp.set_(hp.me.textContent, to=f"Count is: {count_var}"),
43
+ ],
44
+ )
45
+ )[f"Count is: {self.initial_count}"]
46
+ ]
47
+
48
+
49
+ @hp.style(CounterWidget)
50
+ def counter_style():
51
+ return {
52
+ "button": {
53
+ "background-color": "#4A90D9",
54
+ "color": "#ffffff",
55
+ "border": "none",
56
+ "border-radius": "6px",
57
+ "padding": "10px 20px",
58
+ "font-size": "16px",
59
+ "font-weight": "600",
60
+ "cursor": "pointer",
61
+ "transition": "background-color 0.2s ease",
62
+ },
63
+ "button:hover": {"background-color": "#3A7BC8"},
64
+ }
65
+
66
+ ```
67
+ ```sh
68
+ hypie build -i ./app/CounterWidget.py -o ./app/static
69
+ ```
70
+ (or your favourite backend)
71
+ ```python
72
+ import pathlib
73
+
74
+ import htpy
75
+ from fastapi import FastAPI
76
+ from fastapi.staticfiles import StaticFiles
77
+ from fastapi.responses import HTMLResponse
78
+
79
+
80
+ from .CounterWidget import CounterWidget
81
+
82
+
83
+ app = FastAPI()
84
+
85
+ app.mount(
86
+ "/static", StaticFiles(directory=pathlib.Path(__file__).parent.resolve() / "static")
87
+ )
88
+
89
+
90
+ @app.get("/")
91
+ def counter_example(count: int = 0):
92
+ return HTMLResponse(
93
+ htpy.html[
94
+ htpy.head[
95
+ htpy.link(),
96
+ htpy.link(rel="stylesheet", href="/static/_hypie_styles.css"),
97
+ htpy.script(src="https://cdn.jsdelivr.net/npm/hyperscript.org@0.9.93"),
98
+ htpy.title["Counter Example"],
99
+ ],
100
+ htpy.body[CounterWidget(count)],
101
+ ]
102
+ )
103
+
104
+ ```
@@ -0,0 +1,90 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Infrared1029/hypie/main/assets/hypie_logo.jpg" alt="Logo" width="400">
3
+ </p>
4
+
5
+ ## Intro
6
+ Hypie, pronounced "high-pie", is a python library for building web frontends, built on top of [_hyperscript](https://github.com/bigskysoftware/_hyperscript), a scripting language for the web made by the [htmx](https://github.com/bigskysoftware/htmx) folks, and [htpy](https://github.com/pelme/htpy), a library for generating HTML in python.
7
+
8
+ The goal of hypie is to be able to code powerful web frontends, server-side rendered or client-side rendered (or a mix of both), without having to leave python, or switch your favorite backend, using relatively little abstractions and no reactivity.
9
+
10
+ New README in the process, for now enjoy this counter example:
11
+ ```python
12
+ import htpy
13
+ import hypie as hp
14
+
15
+
16
+ class CounterWidget(hp.ServerComponent):
17
+ initial_count: int = 0
18
+
19
+ def template(self):
20
+ count_var = hp.var(":count")
21
+
22
+ return htpy.div[
23
+ htpy.button(
24
+ _=hp.hs(
25
+ hp.set_(count_var, to=self.initial_count),
26
+ hp.On("click")[
27
+ hp.set_(count_var, to=count_var + 1),
28
+ hp.set_(hp.me.textContent, to=f"Count is: {count_var}"),
29
+ ],
30
+ )
31
+ )[f"Count is: {self.initial_count}"]
32
+ ]
33
+
34
+
35
+ @hp.style(CounterWidget)
36
+ def counter_style():
37
+ return {
38
+ "button": {
39
+ "background-color": "#4A90D9",
40
+ "color": "#ffffff",
41
+ "border": "none",
42
+ "border-radius": "6px",
43
+ "padding": "10px 20px",
44
+ "font-size": "16px",
45
+ "font-weight": "600",
46
+ "cursor": "pointer",
47
+ "transition": "background-color 0.2s ease",
48
+ },
49
+ "button:hover": {"background-color": "#3A7BC8"},
50
+ }
51
+
52
+ ```
53
+ ```sh
54
+ hypie build -i ./app/CounterWidget.py -o ./app/static
55
+ ```
56
+ (or your favourite backend)
57
+ ```python
58
+ import pathlib
59
+
60
+ import htpy
61
+ from fastapi import FastAPI
62
+ from fastapi.staticfiles import StaticFiles
63
+ from fastapi.responses import HTMLResponse
64
+
65
+
66
+ from .CounterWidget import CounterWidget
67
+
68
+
69
+ app = FastAPI()
70
+
71
+ app.mount(
72
+ "/static", StaticFiles(directory=pathlib.Path(__file__).parent.resolve() / "static")
73
+ )
74
+
75
+
76
+ @app.get("/")
77
+ def counter_example(count: int = 0):
78
+ return HTMLResponse(
79
+ htpy.html[
80
+ htpy.head[
81
+ htpy.link(),
82
+ htpy.link(rel="stylesheet", href="/static/_hypie_styles.css"),
83
+ htpy.script(src="https://cdn.jsdelivr.net/npm/hyperscript.org@0.9.93"),
84
+ htpy.title["Counter Example"],
85
+ ],
86
+ htpy.body[CounterWidget(count)],
87
+ ]
88
+ )
89
+
90
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hypie"
3
- version = "0.1.1.dev4"
3
+ version = "0.1.1.dev5"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -30,6 +30,7 @@ dev = [
30
30
  ]
31
31
  examples = [
32
32
  "fastapi[standard]>=0.139.0",
33
+ "requests>=2.34.2",
33
34
  ]
34
35
 
35
36
  [[tool.uv.index]]
@@ -0,0 +1,6 @@
1
+ from hypie.literals import *
2
+ from hypie.commands import *
3
+ from hypie.features import *
4
+ from hypie.experimental.server_component import ServerComponent
5
+ from hypie.experimental.client_component import ClientComponent
6
+ from hypie.experimental.style import style
@@ -0,0 +1,142 @@
1
+ import importlib
2
+ import pathlib
3
+ import sys
4
+ import inspect
5
+ import traceback
6
+
7
+ import argparse
8
+ from pathlib import Path
9
+
10
+ from hypie.hs_ast.expressions import Expr
11
+ from hypie.experimental.style import generate_scoped_css
12
+
13
+ import watchfiles
14
+
15
+
16
+ def find_components_register_artifacts(in_path, out_path, out_files_prefix=""):
17
+ IGNORE_LIST = [".venv", ".pyc", ".pyo"]
18
+ MODULES = set()
19
+
20
+ BEHAVIORS = set()
21
+ STYLES = set()
22
+ try:
23
+ path = pathlib.Path(in_path).resolve()
24
+ if not path.exists():
25
+ raise Exception("Input path does not exist")
26
+ # print(path, path.parent.resolve())
27
+ outdir = pathlib.Path(out_path).resolve()
28
+ if outdir.is_file():
29
+ raise Exception("--output must be a dir")
30
+ outdir.mkdir(exist_ok=True, parents=True)
31
+ # print(outdir)
32
+ sys.path.insert(0, str(path.parent.resolve()))
33
+ path_parts_len = len(path.parts)
34
+ if path.is_file():
35
+ python_files = [path]
36
+ else:
37
+ python_files = pathlib.Path(path).glob("**/*.py")
38
+ for file_path in python_files:
39
+ if any(p in file_path.parts for p in IGNORE_LIST):
40
+ continue
41
+ module_name_parts = list(file_path.parts[path_parts_len - 1 : -1]) + [
42
+ file_path.stem
43
+ ]
44
+ module_name = ".".join(module_name_parts)
45
+ mod = importlib.import_module(module_name)
46
+ MODULES.add(module_name)
47
+ for _, obj in inspect.getmembers(mod):
48
+ if isinstance(obj, Expr):
49
+ continue
50
+ elif getattr(obj, "_hs_behavior", False) == True:
51
+ BEHAVIORS.add(obj)
52
+ elif getattr(obj, "_hypie_style", False) == True:
53
+ STYLES.add(obj)
54
+
55
+
56
+ # create css files
57
+ styles = []
58
+ hs_files = []
59
+ html = []
60
+ print(
61
+ f"Done Proccessing: {len(STYLES)} Styles, {len(BEHAVIORS)} Behaviors."
62
+ )
63
+
64
+ for s in STYLES:
65
+ style = s._style
66
+ if style:
67
+ styles.append(generate_scoped_css(", ".join(s._components), style))
68
+
69
+ for b in BEHAVIORS:
70
+ script = b._behavior
71
+ if script:
72
+ hs_files.append(script)
73
+
74
+ style_contents = "\n".join(styles)
75
+ if style_contents:
76
+ with open(outdir / f"{out_files_prefix}_hypie_styles.css", "w") as f:
77
+ f.write(style_contents)
78
+
79
+ html_contents = "\n".join(html)
80
+ if html_contents:
81
+ with open(outdir / f"{out_files_prefix}_hypie_templates.html", "w") as f:
82
+ f.write(html_contents)
83
+
84
+ hs_contents = "\n".join(h.render() for h in hs_files)
85
+ if hs_contents:
86
+ with open(outdir / f"{out_files_prefix}_hypie_hyperscript._hs", "w") as f:
87
+ f.write(hs_contents)
88
+ finally:
89
+ # clean up
90
+ sys.path.pop(0)
91
+ for mod_name in MODULES:
92
+ del sys.modules[mod_name]
93
+
94
+
95
+ def main():
96
+ cli_parser = argparse.ArgumentParser(prog="hypie")
97
+
98
+ subparsers = cli_parser.add_subparsers(dest="command", required=True)
99
+ build_parser = subparsers.add_parser("build", help="Build components")
100
+ build_parser.add_argument(
101
+ "--input", "-i", type=Path, required=True, help="Path to components dir"
102
+ )
103
+ build_parser.add_argument(
104
+ "--output", "-o", type=Path, required=True, help="Output path for file"
105
+ )
106
+ build_parser.add_argument(
107
+ "--prefix",
108
+ "-p",
109
+ type=str,
110
+ required=False,
111
+ default="",
112
+ help="output files prefix",
113
+ )
114
+ build_parser.add_argument(
115
+ "--watch",
116
+ "-w",
117
+ action="store_true",
118
+ required=False,
119
+ default=False,
120
+ help="watch dir and re-run build",
121
+ )
122
+ args = cli_parser.parse_args()
123
+
124
+ if args.command == "build":
125
+ find_components_register_artifacts(
126
+ out_files_prefix=args.prefix, in_path=args.input, out_path=args.output
127
+ )
128
+ if args.watch:
129
+ print("[hypie]: waiting for changes...")
130
+ for _ in watchfiles.watch(args.input):
131
+ print("[hypie]: detected changes, re-running build...")
132
+ try:
133
+ find_components_register_artifacts(
134
+ out_files_prefix=args.prefix,
135
+ in_path=args.input,
136
+ out_path=args.output,
137
+ )
138
+ except KeyboardInterrupt:
139
+ exit()
140
+ except Exception as e:
141
+ traceback.print_exc()
142
+ print("[hypie]: waiting for changes...")
@@ -3,8 +3,7 @@ from typing import overload
3
3
  from hypie.hs_ast.commands import *
4
4
  from hypie.hs_ast.expressions import *
5
5
  from hypie.events import Event
6
- from hypie.experimental.templates import Template
7
- # from hypie.experimental.client_components import ClientComponent
6
+ from hypie.experimental.client_component import ClientComponent
8
7
 
9
8
 
10
9
  @overload
@@ -128,26 +127,6 @@ def set_(set_expr: VariableLiteral, /, to: Expr):
128
127
 
129
128
 
130
129
  def put(expr: Expr, /, placement: PUT_PLACEMENTS, target: Expr):
131
- if (
132
- hasattr(expr, "_hs_dsl_client_component")
133
- and expr._hs_dsl_client_component == True
134
- ):
135
- # expr = TemplateLiteral(expr.__html__())
136
- tag = f"<{expr.component_name}/>"
137
- # f:python_to_hs(getattr(self, f)) for f in self.field_names
138
- commands = [make(tag)]
139
- # for f in expr.field_names
140
- return [
141
- make(tag),
142
- [
143
- set_(
144
- VariableLiteral(f"result's @{f}"),
145
- to=VariableLiteral(f"`'${{{getattr(expr, f)}}}'`"),
146
- )
147
- for f in expr.field_names
148
- ],
149
- Put(expr=VariableLiteral("result"), placement=placement, target=target),
150
- ]
151
130
  return Put(expr=expr, placement=placement, target=target)
152
131
 
153
132
 
@@ -211,10 +190,25 @@ def make(object_name: str, /, *from_: Expr | list[Expr], called: str = None):
211
190
  return Make(object_name=object_name, from_=from_, called=called)
212
191
 
213
192
 
214
- def render(template: Template):
215
- if isinstance(template, Template):
193
+ def render(template: ClientComponent):
194
+ def extract_expr(v):
195
+ if isinstance(v, TemplatedVariableLiteral):
196
+ return v.symbol
197
+ if isinstance(template, (ClientComponent)):
216
198
  return Render(
217
- template_id=template.id,
218
- with_={f: getattr(template, f) for f in template.field_names},
199
+ template_id=template._template_id,
200
+ with_={f: extract_expr(getattr(template, f)) for f in template.field_names},
219
201
  )
220
- raise Exception("render only accepts Template objects")
202
+ raise Exception("render only accepts Client Fragments")
203
+
204
+ def append(expr: Expr, / , to):
205
+ return Append(expr=expr, to=to)
206
+
207
+ def js(*vars):
208
+ _vars = []
209
+ for v in vars:
210
+ if isinstance(v, VariableLiteral):
211
+ _vars.append(v.symbol)
212
+ else:
213
+ _vars.append(v)
214
+ return Js(vars=_vars)
@@ -1,77 +1,77 @@
1
- from hypie.literals import var, Expr, TimeLiteral
2
- from typing import Literal, dataclass_transform
3
- from dataclasses import fields, dataclass, is_dataclass
4
-
5
-
6
- @dataclass
7
- class EventSpec:
8
- filter: Expr = (None,)
9
- count: int = (None,)
10
- from_: Expr | Literal["elsewhere"] = (None,)
11
- in_: Expr = (None,)
12
- debounce: TimeLiteral = (None,)
13
- throttle: TimeLiteral = (None,)
14
-
15
-
16
- @dataclass_transform()
17
- class Event:
18
- event_spec: EventSpec = None
19
- event_name: str = None
20
- event_args: list = None
21
-
22
- def __init_subclass__(cls):
23
- if not is_dataclass(cls):
24
- dataclass_cls = dataclass(cls)
25
- else:
26
- dataclass_cls = cls
27
- dataclass_cls.event_name = cls.__name__
28
- args = []
29
- for f in fields(dataclass_cls):
30
- setattr(cls, f.name, var(f"{cls.event_name}__{f.name}"))
31
- args.append(f"{cls.event_name}__{f.name}")
32
- cls.event_args = args
33
-
34
- @property
35
- def _bounded_args(self):
36
- # if not TEMPLATE_CONTEXT.get():
37
- return {
38
- f"{self.event_name}__{f.name}": getattr(self, f.name) for f in fields(self)
39
- }
40
- # template
41
- # out = {}
42
- # for f in fields(self):
43
- # v = getattr(self, f.name)
44
- # if isinstance(v, VariableLiteral):
45
- # out[f"{self.event_name}__{f.name}"] = VariableLiteral(f"${{{v.render()}}}")
46
- # else:
47
- # out[f"{self.event_name}__{f.name}"] = v
48
-
49
- # return out
50
-
51
- @classmethod
52
- def with_spec(
53
- cls,
54
- filter=None,
55
- count: int = None,
56
- from_: Expr | Literal["elsewhere"] = None,
57
- in_: Expr = None,
58
- debounce: TimeLiteral = None,
59
- throttle: TimeLiteral = None,
60
- ):
61
- spec = EventSpec(
62
- filter=filter,
63
- count=count,
64
- from_=from_,
65
- in_=in_,
66
- debounce=debounce,
67
- throttle=throttle,
68
- )
69
- return type(cls)(
70
- cls.__name__,
71
- (cls,),
72
- {
73
- "event_spec": spec,
74
- "__qualname__": cls.__qualname__,
75
- "__module__": cls.__module__,
76
- },
77
- )
1
+ from hypie.literals import var, Expr, TimeLiteral, python_to_hs
2
+ from typing import Literal, dataclass_transform
3
+ from dataclasses import fields, dataclass, is_dataclass
4
+
5
+
6
+ @dataclass
7
+ class EventSpec:
8
+ filter: Expr = (None,)
9
+ count: int = (None,)
10
+ from_: Expr | Literal["elsewhere"] = (None,)
11
+ in_: Expr = (None,)
12
+ debounce: TimeLiteral = (None,)
13
+ throttle: TimeLiteral = (None,)
14
+
15
+
16
+ @dataclass_transform()
17
+ class Event:
18
+ event_spec: EventSpec = None
19
+ event_name: str = None
20
+ event_args: list = None
21
+
22
+ def __init_subclass__(cls):
23
+ if not is_dataclass(cls):
24
+ dataclass_cls = dataclass(cls)
25
+ else:
26
+ dataclass_cls = cls
27
+ dataclass_cls.event_name = cls.__name__
28
+ args = []
29
+ for f in fields(dataclass_cls):
30
+ setattr(cls, f.name, var(f"{cls.event_name}__{f.name}"))
31
+ args.append(f"{cls.event_name}__{f.name}")
32
+ cls.event_args = args
33
+
34
+ @property
35
+ def _bounded_args(self):
36
+ # if not TEMPLATE_CONTEXT.get():
37
+ return {
38
+ f"{self.event_name}__{f.name}": getattr(self, f.name) for f in fields(self)
39
+ }
40
+ # template
41
+ # out = {}
42
+ # for f in fields(self):
43
+ # v = getattr(self, f.name)
44
+ # if isinstance(v, VariableLiteral):
45
+ # out[f"{self.event_name}__{f.name}"] = VariableLiteral(f"${{{v.render()}}}")
46
+ # else:
47
+ # out[f"{self.event_name}__{f.name}"] = v
48
+
49
+ # return out
50
+
51
+ @classmethod
52
+ def with_spec(
53
+ cls,
54
+ filter=None,
55
+ count: int = None,
56
+ from_: Expr | Literal["elsewhere"] = None,
57
+ in_: Expr = None,
58
+ debounce: TimeLiteral = None,
59
+ throttle: TimeLiteral = None,
60
+ ):
61
+ spec = EventSpec(
62
+ filter=filter,
63
+ count=count,
64
+ from_=from_,
65
+ in_=in_,
66
+ debounce=debounce,
67
+ throttle=throttle,
68
+ )
69
+ return type(cls)(
70
+ cls.__name__,
71
+ (cls,),
72
+ {
73
+ "event_spec": spec,
74
+ "__qualname__": cls.__qualname__,
75
+ "__module__": cls.__module__,
76
+ },
77
+ )