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/request.py ADDED
@@ -0,0 +1,205 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping, MutableMapping
4
+ from http.cookies import SimpleCookie
5
+ from typing import Any, cast
6
+
7
+
8
+ def _bytes_kv_to_str(headers: list[tuple[bytes, bytes]]) -> dict[str, str]:
9
+ out: dict[str, str] = {}
10
+ for k, v in headers:
11
+ try:
12
+ out[k.decode("latin1").lower()] = v.decode("latin1")
13
+ except Exception:
14
+ # Best effort
15
+ out[str(k).lower()] = str(v)
16
+ return out
17
+
18
+
19
+ class PulseRequest:
20
+ """Normalized request object for both HTTP prerender and WebSocket connect.
21
+
22
+ Provides a consistent interface for accessing request data regardless of
23
+ the underlying transport (FastAPI/Starlette HTTP or Socket.IO WebSocket).
24
+
25
+ Attributes:
26
+ headers: Request headers with lowercased keys.
27
+ cookies: Request cookies as name-value pairs.
28
+ scheme: URL scheme (http/https).
29
+ method: HTTP method (GET, POST, etc.).
30
+ path: URL path.
31
+ query_string: Query string (without leading ?).
32
+ client: Client address as (host, port) tuple, or None.
33
+ auth: Auth data (Socket.IO only).
34
+ raw: Underlying request object for advanced use.
35
+
36
+ Args:
37
+ headers: Request headers (keys will be lowercased).
38
+ cookies: Request cookies.
39
+ scheme: URL scheme (http/https).
40
+ method: HTTP method.
41
+ path: URL path.
42
+ query_string: Query string (without ?).
43
+ client: Client address as (host, port) tuple.
44
+ auth: Auth data (for Socket.IO).
45
+ raw: Underlying request object.
46
+ """
47
+
48
+ headers: dict[str, str]
49
+ cookies: dict[str, str]
50
+ scheme: str
51
+ method: str
52
+ path: str
53
+ query_string: str
54
+ client: tuple[str, int] | None
55
+ auth: Any | None
56
+ raw: Any | None
57
+
58
+ def __init__(
59
+ self,
60
+ *,
61
+ headers: Mapping[str, str] | None = None,
62
+ cookies: Mapping[str, str] | None = None,
63
+ scheme: str | None = None,
64
+ method: str | None = None,
65
+ path: str | None = None,
66
+ query_string: str | None = None,
67
+ client: tuple[str, int] | None = None,
68
+ auth: Any | None = None,
69
+ raw: Any | None = None,
70
+ ) -> None:
71
+ self.headers = {k.lower(): v for k, v in (headers or {}).items()}
72
+ self.cookies = dict(cookies or {})
73
+ self.scheme = scheme or ""
74
+ self.method = method or ""
75
+ self.path = path or ""
76
+ self.query_string = query_string or ""
77
+ self.client = client
78
+ self.auth = auth
79
+ self.raw = raw
80
+
81
+ @property
82
+ def url(self) -> str:
83
+ """Full URL including scheme, host, path, and query string."""
84
+ qs = f"?{self.query_string}" if self.query_string else ""
85
+ host = self.headers.get("host", "")
86
+ if host:
87
+ return f"{self.scheme or 'http'}://{host}{self.path}{qs}"
88
+ return f"{self.path}{qs}"
89
+
90
+ @staticmethod
91
+ def from_fastapi(request: Any) -> "PulseRequest":
92
+ """Create from a FastAPI/Starlette request.
93
+
94
+ Args:
95
+ request: FastAPI/Starlette Request object.
96
+
97
+ Returns:
98
+ PulseRequest instance with normalized request data.
99
+ """
100
+ # FastAPI/Starlette Request
101
+ headers = {k.lower(): v for k, v in request.headers.items()}
102
+ cookies = dict(request.cookies or {})
103
+ scheme = request.url.scheme
104
+ method = request.method
105
+ path = request.url.path
106
+ query_string = request.url.query or ""
107
+ client = (request.client.host, request.client.port) if request.client else None
108
+ return PulseRequest(
109
+ headers=headers,
110
+ cookies=cookies,
111
+ scheme=scheme,
112
+ method=method,
113
+ path=path,
114
+ query_string=query_string,
115
+ client=client,
116
+ raw=request,
117
+ )
118
+
119
+ @staticmethod
120
+ def from_socketio_environ(
121
+ environ: MutableMapping[str, Any], auth: Any | None
122
+ ) -> "PulseRequest":
123
+ """Create from a Socket.IO environ dictionary.
124
+
125
+ Args:
126
+ environ: Socket.IO environ dictionary (WSGI or ASGI-like).
127
+ auth: Auth data passed during Socket.IO connect.
128
+
129
+ Returns:
130
+ PulseRequest instance with normalized request data.
131
+ """
132
+ # python-socketio passes a WSGI/ASGI-like environ. Try to detect ASGI scope first.
133
+ scope: MutableMapping[str, Any] = environ.get("asgi.scope") or environ
134
+
135
+ headers: dict[str, str] = {}
136
+ cookies: dict[str, str] = {}
137
+ scheme = ""
138
+ method = ""
139
+ path = ""
140
+ query_string = ""
141
+ client: tuple[str, int] | None = None
142
+
143
+ if isinstance(scope, Mapping) and "type" in scope: # ASGI scope
144
+ scheme = scope.get("scheme", "")
145
+ method = scope.get("method", "") or "GET"
146
+ path = scope.get("path", "")
147
+ raw_qs = scope.get("query_string", b"") or b""
148
+ try:
149
+ query_string = raw_qs.decode("latin1")
150
+ except Exception:
151
+ query_string = ""
152
+ asgi_headers = cast(
153
+ list[tuple[bytes, bytes]], scope.get("headers", []) or []
154
+ )
155
+ headers = _bytes_kv_to_str(asgi_headers)
156
+ # Cookies from header if present
157
+ cookie_header = headers.get("cookie")
158
+ if cookie_header:
159
+ sc = SimpleCookie()
160
+ sc.load(cookie_header)
161
+ cookies = {k: v.value for k, v in sc.items()}
162
+ scope_client = scope.get("client")
163
+ if scope_client:
164
+ client = tuple(scope_client)
165
+ else:
166
+ # WSGI-like environ
167
+ scheme = scope.get("wsgi.url_scheme", "") or scope.get("scheme", "")
168
+ method = scope.get("REQUEST_METHOD", "GET")
169
+ path = scope.get("PATH_INFO", "")
170
+ query_string = scope.get("QUERY_STRING", "")
171
+ # headers from HTTP_* keys
172
+ for k, v in scope.items():
173
+ if isinstance(k, str) and k.startswith("HTTP_"):
174
+ name = k[5:].replace("_", "-").lower()
175
+ headers[name] = str(v)
176
+ if "CONTENT_TYPE" in scope:
177
+ headers["content-type"] = str(scope["CONTENT_TYPE"]) # type: ignore
178
+ if "CONTENT_LENGTH" in scope:
179
+ headers["content-length"] = str(scope["CONTENT_LENGTH"]) # type: ignore
180
+ # Cookies
181
+ cookie_header = headers.get("cookie") or scope.get("HTTP_COOKIE")
182
+ if cookie_header:
183
+ sc = SimpleCookie()
184
+ sc.load(cookie_header) # type: ignore[arg-type]
185
+ cookies = {k: v.value for k, v in sc.items()}
186
+ # client is not standard in WSGI; try remote addr
187
+ remote = scope.get("REMOTE_ADDR")
188
+ port = scope.get("REMOTE_PORT")
189
+ if remote is not None and port is not None:
190
+ try:
191
+ client = (str(remote), int(port))
192
+ except Exception:
193
+ client = None
194
+
195
+ return PulseRequest(
196
+ headers=headers,
197
+ cookies=cookies,
198
+ scheme=scheme,
199
+ method=method,
200
+ path=path,
201
+ query_string=query_string,
202
+ client=client,
203
+ auth=auth,
204
+ raw=environ,
205
+ )