pulse-framework 0.1.62__py3-none-any.whl

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 (126) hide show
  1. pulse/__init__.py +1493 -0
  2. pulse/_examples.py +29 -0
  3. pulse/app.py +1086 -0
  4. pulse/channel.py +607 -0
  5. pulse/cli/__init__.py +0 -0
  6. pulse/cli/cmd.py +575 -0
  7. pulse/cli/dependencies.py +181 -0
  8. pulse/cli/folder_lock.py +134 -0
  9. pulse/cli/helpers.py +271 -0
  10. pulse/cli/logging.py +102 -0
  11. pulse/cli/models.py +35 -0
  12. pulse/cli/packages.py +262 -0
  13. pulse/cli/processes.py +292 -0
  14. pulse/cli/secrets.py +39 -0
  15. pulse/cli/uvicorn_log_config.py +87 -0
  16. pulse/code_analysis.py +38 -0
  17. pulse/codegen/__init__.py +0 -0
  18. pulse/codegen/codegen.py +359 -0
  19. pulse/codegen/templates/__init__.py +0 -0
  20. pulse/codegen/templates/layout.py +106 -0
  21. pulse/codegen/templates/route.py +345 -0
  22. pulse/codegen/templates/routes_ts.py +42 -0
  23. pulse/codegen/utils.py +20 -0
  24. pulse/component.py +237 -0
  25. pulse/components/__init__.py +0 -0
  26. pulse/components/for_.py +83 -0
  27. pulse/components/if_.py +86 -0
  28. pulse/components/react_router.py +94 -0
  29. pulse/context.py +108 -0
  30. pulse/cookies.py +322 -0
  31. pulse/decorators.py +344 -0
  32. pulse/dom/__init__.py +0 -0
  33. pulse/dom/elements.py +1024 -0
  34. pulse/dom/events.py +445 -0
  35. pulse/dom/props.py +1250 -0
  36. pulse/dom/svg.py +0 -0
  37. pulse/dom/tags.py +328 -0
  38. pulse/dom/tags.pyi +480 -0
  39. pulse/env.py +178 -0
  40. pulse/form.py +538 -0
  41. pulse/helpers.py +541 -0
  42. pulse/hooks/__init__.py +0 -0
  43. pulse/hooks/core.py +452 -0
  44. pulse/hooks/effects.py +88 -0
  45. pulse/hooks/init.py +668 -0
  46. pulse/hooks/runtime.py +464 -0
  47. pulse/hooks/setup.py +254 -0
  48. pulse/hooks/stable.py +138 -0
  49. pulse/hooks/state.py +192 -0
  50. pulse/js/__init__.py +125 -0
  51. pulse/js/__init__.pyi +115 -0
  52. pulse/js/_types.py +299 -0
  53. pulse/js/array.py +339 -0
  54. pulse/js/console.py +50 -0
  55. pulse/js/date.py +119 -0
  56. pulse/js/document.py +145 -0
  57. pulse/js/error.py +140 -0
  58. pulse/js/json.py +66 -0
  59. pulse/js/map.py +97 -0
  60. pulse/js/math.py +69 -0
  61. pulse/js/navigator.py +79 -0
  62. pulse/js/number.py +57 -0
  63. pulse/js/obj.py +81 -0
  64. pulse/js/object.py +172 -0
  65. pulse/js/promise.py +172 -0
  66. pulse/js/pulse.py +115 -0
  67. pulse/js/react.py +495 -0
  68. pulse/js/regexp.py +57 -0
  69. pulse/js/set.py +124 -0
  70. pulse/js/string.py +38 -0
  71. pulse/js/weakmap.py +53 -0
  72. pulse/js/weakset.py +48 -0
  73. pulse/js/window.py +205 -0
  74. pulse/messages.py +202 -0
  75. pulse/middleware.py +471 -0
  76. pulse/plugin.py +96 -0
  77. pulse/proxy.py +242 -0
  78. pulse/py.typed +0 -0
  79. pulse/queries/__init__.py +0 -0
  80. pulse/queries/client.py +609 -0
  81. pulse/queries/common.py +101 -0
  82. pulse/queries/effect.py +55 -0
  83. pulse/queries/infinite_query.py +1418 -0
  84. pulse/queries/mutation.py +295 -0
  85. pulse/queries/protocol.py +136 -0
  86. pulse/queries/query.py +1314 -0
  87. pulse/queries/store.py +120 -0
  88. pulse/react_component.py +88 -0
  89. pulse/reactive.py +1208 -0
  90. pulse/reactive_extensions.py +1172 -0
  91. pulse/render_session.py +768 -0
  92. pulse/renderer.py +584 -0
  93. pulse/request.py +205 -0
  94. pulse/routing.py +598 -0
  95. pulse/serializer.py +279 -0
  96. pulse/state.py +556 -0
  97. pulse/test_helpers.py +15 -0
  98. pulse/transpiler/__init__.py +111 -0
  99. pulse/transpiler/assets.py +81 -0
  100. pulse/transpiler/builtins.py +1029 -0
  101. pulse/transpiler/dynamic_import.py +130 -0
  102. pulse/transpiler/emit_context.py +49 -0
  103. pulse/transpiler/errors.py +96 -0
  104. pulse/transpiler/function.py +611 -0
  105. pulse/transpiler/id.py +18 -0
  106. pulse/transpiler/imports.py +341 -0
  107. pulse/transpiler/js_module.py +336 -0
  108. pulse/transpiler/modules/__init__.py +33 -0
  109. pulse/transpiler/modules/asyncio.py +57 -0
  110. pulse/transpiler/modules/json.py +24 -0
  111. pulse/transpiler/modules/math.py +265 -0
  112. pulse/transpiler/modules/pulse/__init__.py +5 -0
  113. pulse/transpiler/modules/pulse/tags.py +250 -0
  114. pulse/transpiler/modules/typing.py +63 -0
  115. pulse/transpiler/nodes.py +1987 -0
  116. pulse/transpiler/py_module.py +135 -0
  117. pulse/transpiler/transpiler.py +1100 -0
  118. pulse/transpiler/vdom.py +256 -0
  119. pulse/types/__init__.py +0 -0
  120. pulse/types/event_handler.py +50 -0
  121. pulse/user_session.py +386 -0
  122. pulse/version.py +69 -0
  123. pulse_framework-0.1.62.dist-info/METADATA +198 -0
  124. pulse_framework-0.1.62.dist-info/RECORD +126 -0
  125. pulse_framework-0.1.62.dist-info/WHEEL +4 -0
  126. pulse_framework-0.1.62.dist-info/entry_points.txt +3 -0
pulse/queries/store.py ADDED
@@ -0,0 +1,120 @@
1
+ import datetime as dt
2
+ from collections.abc import Callable
3
+ from typing import Any, TypeVar, cast
4
+
5
+ from pulse.helpers import MISSING
6
+ from pulse.queries.common import QueryKey
7
+ from pulse.queries.infinite_query import InfiniteQuery, Page
8
+ from pulse.queries.query import RETRY_DELAY_DEFAULT, KeyedQuery
9
+
10
+ T = TypeVar("T")
11
+
12
+
13
+ class QueryStore:
14
+ """
15
+ Store for query entries. Manages creation, retrieval, and disposal of queries.
16
+ """
17
+
18
+ def __init__(self):
19
+ self._entries: dict[QueryKey, KeyedQuery[Any] | InfiniteQuery[Any, Any]] = {}
20
+
21
+ def items(self):
22
+ """Iterate over all (key, query) pairs in the store."""
23
+ return self._entries.items()
24
+
25
+ def get_any(self, key: QueryKey):
26
+ """Get any query (regular or infinite) by key, or None if not found."""
27
+ return self._entries.get(key)
28
+
29
+ def ensure(
30
+ self,
31
+ key: QueryKey,
32
+ initial_data: T | None = MISSING,
33
+ initial_data_updated_at: float | dt.datetime | None = None,
34
+ gc_time: float = 300.0,
35
+ retries: int = 3,
36
+ retry_delay: float = RETRY_DELAY_DEFAULT,
37
+ ) -> KeyedQuery[T]:
38
+ # Return existing entry if present
39
+ existing = self._entries.get(key)
40
+ if existing:
41
+ if isinstance(existing, InfiniteQuery):
42
+ raise TypeError(
43
+ "Query key is already used for an infinite query; cannot reuse for regular query"
44
+ )
45
+ return cast(KeyedQuery[T], existing)
46
+
47
+ def _on_dispose(e: KeyedQuery[Any]) -> None:
48
+ if e.key in self._entries and self._entries[e.key] is e:
49
+ del self._entries[e.key]
50
+
51
+ entry = KeyedQuery(
52
+ key,
53
+ initial_data=initial_data,
54
+ initial_data_updated_at=initial_data_updated_at,
55
+ gc_time=gc_time,
56
+ retries=retries,
57
+ retry_delay=retry_delay,
58
+ on_dispose=_on_dispose,
59
+ )
60
+ self._entries[key] = entry
61
+ return entry
62
+
63
+ def get(self, key: QueryKey) -> KeyedQuery[Any] | None:
64
+ """
65
+ Get an existing regular query by key, or None if not found.
66
+ """
67
+ existing = self._entries.get(key)
68
+ if existing and isinstance(existing, InfiniteQuery):
69
+ return None
70
+ return existing
71
+
72
+ def get_infinite(self, key: QueryKey) -> InfiniteQuery[Any, Any] | None:
73
+ """
74
+ Get an existing infinite query by key, or None if not found.
75
+ """
76
+ existing = self._entries.get(key)
77
+ if existing and isinstance(existing, InfiniteQuery):
78
+ return existing
79
+ return None
80
+
81
+ def ensure_infinite(
82
+ self,
83
+ key: QueryKey,
84
+ *,
85
+ initial_page_param: Any,
86
+ get_next_page_param: Callable[[list[Page[Any, Any]]], Any | None],
87
+ get_previous_page_param: Callable[[list[Page[Any, Any]]], Any | None]
88
+ | None = None,
89
+ max_pages: int = 0,
90
+ initial_data_updated_at: float | dt.datetime | None = None,
91
+ gc_time: float = 300.0,
92
+ retries: int = 3,
93
+ retry_delay: float = RETRY_DELAY_DEFAULT,
94
+ ) -> InfiniteQuery[Any, Any]:
95
+ existing = self._entries.get(key)
96
+ if existing:
97
+ if not isinstance(existing, InfiniteQuery):
98
+ raise TypeError(
99
+ "Query key is already used for a regular query; cannot reuse for infinite query"
100
+ )
101
+ return existing
102
+
103
+ def _on_dispose(e: InfiniteQuery[Any, Any]) -> None:
104
+ if e.key in self._entries and self._entries[e.key] is e:
105
+ del self._entries[e.key]
106
+
107
+ entry = InfiniteQuery(
108
+ key,
109
+ initial_page_param=initial_page_param,
110
+ get_next_page_param=get_next_page_param,
111
+ get_previous_page_param=get_previous_page_param,
112
+ max_pages=max_pages,
113
+ initial_data_updated_at=initial_data_updated_at,
114
+ gc_time=gc_time,
115
+ retries=retries,
116
+ retry_delay=retry_delay,
117
+ on_dispose=_on_dispose,
118
+ )
119
+ self._entries[key] = entry
120
+ return entry
@@ -0,0 +1,88 @@
1
+ """React component helpers for Python API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any, ParamSpec, overload
7
+
8
+ from pulse.js.react import lazy as react_lazy
9
+ from pulse.transpiler.imports import Import
10
+ from pulse.transpiler.nodes import Element, Expr, Jsx, Node
11
+
12
+ P = ParamSpec("P")
13
+
14
+
15
+ def default_signature(
16
+ *children: Node, key: str | None = None, **props: Any
17
+ ) -> Element: ...
18
+
19
+
20
+ class ReactComponent(Jsx):
21
+ """JSX wrapper for React components with runtime call support."""
22
+
23
+ def __init__(self, expr_or_src: Expr | str, *, lazy: bool = False) -> None:
24
+ if isinstance(expr_or_src, str):
25
+ if lazy:
26
+ expr: Expr = react_lazy(Import(expr_or_src, lazy=True))
27
+ else:
28
+ expr = Import(expr_or_src)
29
+ else:
30
+ if lazy:
31
+ raise TypeError(
32
+ "ReactComponent lazy only supported with a source string"
33
+ )
34
+ expr = expr_or_src
35
+ if not isinstance(expr, Expr):
36
+ raise TypeError("ReactComponent expects an Expr or source string")
37
+ if isinstance(expr, Jsx):
38
+ expr = expr.expr
39
+ super().__init__(expr)
40
+
41
+
42
+ @overload
43
+ def react_component(
44
+ expr_or_name: Expr,
45
+ ) -> Callable[[Callable[P, Any]], Callable[P, Element]]: ...
46
+
47
+
48
+ @overload
49
+ def react_component(
50
+ expr_or_name: str,
51
+ src: str | None = None,
52
+ *,
53
+ lazy: bool = False,
54
+ ) -> Callable[[Callable[P, Any]], Callable[P, Element]]: ...
55
+
56
+
57
+ def react_component(
58
+ expr_or_name: Expr | str,
59
+ src: str | None = None,
60
+ *,
61
+ lazy: bool = False,
62
+ ) -> Callable[[Callable[P, Any]], Callable[P, Element]]:
63
+ """Decorator for typed React component bindings."""
64
+ if isinstance(expr_or_name, Expr):
65
+ if src is not None:
66
+ raise TypeError("react_component expects (expr) or (name, src)")
67
+ if lazy:
68
+ raise TypeError("react_component lazy only supported with string inputs")
69
+ component = ReactComponent(expr_or_name)
70
+ elif isinstance(expr_or_name, str):
71
+ if src is None:
72
+ component = ReactComponent(expr_or_name, lazy=lazy)
73
+ else:
74
+ imp = Import(expr_or_name, src, lazy=lazy)
75
+ if lazy:
76
+ component = ReactComponent(react_lazy(imp))
77
+ else:
78
+ component = ReactComponent(imp)
79
+ else:
80
+ raise TypeError("react_component expects an Expr or (name, src)")
81
+
82
+ def decorator(fn: Callable[P, Any]) -> Callable[P, Element]:
83
+ return component.as_(fn)
84
+
85
+ return decorator
86
+
87
+
88
+ __all__ = ["ReactComponent", "react_component", "default_signature"]