pulse-framework 0.1.47__tar.gz → 0.1.48__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 (118) hide show
  1. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/PKG-INFO +1 -1
  2. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/pyproject.toml +1 -1
  3. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/app.py +4 -1
  4. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/processes.py +1 -0
  5. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/helpers.py +11 -19
  6. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/proxy.py +21 -8
  7. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/README.md +0 -0
  8. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/__init__.py +0 -0
  9. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/channel.py +0 -0
  10. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/__init__.py +0 -0
  11. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/cmd.py +0 -0
  12. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/dependencies.py +0 -0
  13. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/folder_lock.py +0 -0
  14. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/helpers.py +0 -0
  15. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/models.py +0 -0
  16. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/packages.py +0 -0
  17. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/secrets.py +0 -0
  18. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cli/uvicorn_log_config.py +0 -0
  19. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/codegen/__init__.py +0 -0
  20. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/codegen/codegen.py +0 -0
  21. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/codegen/js.py +0 -0
  22. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/codegen/templates/__init__.py +0 -0
  23. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/codegen/templates/layout.py +0 -0
  24. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/codegen/templates/route.py +0 -0
  25. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/codegen/templates/routes_ts.py +0 -0
  26. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/codegen/utils.py +0 -0
  27. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/components/__init__.py +0 -0
  28. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/components/for_.py +0 -0
  29. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/components/if_.py +0 -0
  30. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/components/react_router.py +0 -0
  31. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/context.py +0 -0
  32. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/cookies.py +0 -0
  33. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/decorators.py +0 -0
  34. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/env.py +0 -0
  35. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/form.py +0 -0
  36. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/hooks/__init__.py +0 -0
  37. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/hooks/core.py +0 -0
  38. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/hooks/effects.py +0 -0
  39. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/hooks/init.py +0 -0
  40. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/hooks/runtime.py +0 -0
  41. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/hooks/setup.py +0 -0
  42. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/hooks/stable.py +0 -0
  43. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/hooks/states.py +0 -0
  44. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/html/__init__.py +0 -0
  45. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/html/elements.py +0 -0
  46. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/html/events.py +0 -0
  47. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/html/props.py +0 -0
  48. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/html/svg.py +0 -0
  49. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/html/tags.py +0 -0
  50. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/html/tags.pyi +0 -0
  51. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/__init__.py +0 -0
  52. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/__init__.pyi +0 -0
  53. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/_types.py +0 -0
  54. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/array.py +0 -0
  55. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/console.py +0 -0
  56. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/date.py +0 -0
  57. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/document.py +0 -0
  58. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/error.py +0 -0
  59. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/json.py +0 -0
  60. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/map.py +0 -0
  61. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/math.py +0 -0
  62. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/navigator.py +0 -0
  63. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/number.py +0 -0
  64. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/object.py +0 -0
  65. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/promise.py +0 -0
  66. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/regexp.py +0 -0
  67. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/set.py +0 -0
  68. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/string.py +0 -0
  69. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/weakmap.py +0 -0
  70. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/weakset.py +0 -0
  71. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/js/window.py +0 -0
  72. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/messages.py +0 -0
  73. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/middleware.py +0 -0
  74. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/plugin.py +0 -0
  75. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/py.typed +0 -0
  76. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/__init__.py +0 -0
  77. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/client.py +0 -0
  78. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/common.py +0 -0
  79. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/effect.py +0 -0
  80. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/infinite_query.py +0 -0
  81. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/mutation.py +0 -0
  82. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/protocol.py +0 -0
  83. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/query.py +0 -0
  84. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/queries/store.py +0 -0
  85. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/react_component.py +0 -0
  86. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/reactive.py +0 -0
  87. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/reactive_extensions.py +0 -0
  88. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/render_session.py +0 -0
  89. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/renderer.py +0 -0
  90. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/request.py +0 -0
  91. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/routing.py +0 -0
  92. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/serializer.py +0 -0
  93. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/state.py +0 -0
  94. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/__init__.py +0 -0
  95. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/builtins.py +0 -0
  96. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/constants.py +0 -0
  97. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/context.py +0 -0
  98. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/errors.py +0 -0
  99. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/function.py +0 -0
  100. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/ids.py +0 -0
  101. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/imports.py +0 -0
  102. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/js_module.py +0 -0
  103. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/modules/__init__.py +0 -0
  104. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/modules/asyncio.py +0 -0
  105. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/modules/json.py +0 -0
  106. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/modules/math.py +0 -0
  107. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/modules/re.py +0 -0
  108. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/modules/tags.py +0 -0
  109. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/modules/typing.py +0 -0
  110. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/nodes.py +0 -0
  111. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/py_module.py +0 -0
  112. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/transpiler.py +0 -0
  113. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/transpiler/utils.py +0 -0
  114. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/types/__init__.py +0 -0
  115. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/types/event_handler.py +0 -0
  116. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/user_session.py +0 -0
  117. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/vdom.py +0 -0
  118. {pulse_framework-0.1.47 → pulse_framework-0.1.48}/src/pulse/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pulse-framework
3
- Version: 0.1.47
3
+ Version: 0.1.48
4
4
  Summary: Pulse - Full-stack framework for building real-time React applications in Python
5
5
  Requires-Dist: websockets>=12.0
6
6
  Requires-Dist: fastapi>=0.104.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pulse-framework"
3
- version = "0.1.47"
3
+ version = "0.1.48"
4
4
  description = "Pulse - Full-stack framework for building real-time React applications in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -576,7 +576,10 @@ class App:
576
576
  + "Use 'pulse run' CLI command or set the environment variable."
577
577
  )
578
578
 
579
- proxy_handler = ReactProxy(react_server_address)
579
+ proxy_handler = ReactProxy(
580
+ react_server_address=react_server_address,
581
+ server_address=server_address,
582
+ )
580
583
 
581
584
  @self.fastapi.api_route(
582
585
  "/{path:path}",
@@ -257,6 +257,7 @@ def _write_tagged_line(name: str, message: str, colors: Mapping[str, str]) -> No
257
257
  if (
258
258
  "Network: use --host to expose" in clean_message
259
259
  or "press h + enter to show help" in clean_message
260
+ or "➜ Local:" in clean_message
260
261
  ):
261
262
  return
262
263
 
@@ -364,11 +364,11 @@ def get_client_address(request: Request) -> str | None:
364
364
  """Best-effort client origin/address from an HTTP request.
365
365
 
366
366
  Preference order:
367
- 1) Origin (full scheme://host:port)
368
- 1b) Referer (full URL) when Origin missing during prerender forwarding
367
+ 1) Origin header (full scheme://host:port)
368
+ 1b) Referer header (full URL) when Origin missing
369
369
  2) Forwarded header (proto + for)
370
370
  3) X-Forwarded-* headers
371
- 4) request.client host:port
371
+ 4) Host header (server address the client connected to)
372
372
  """
373
373
  try:
374
374
  origin = request.headers.get("origin")
@@ -402,14 +402,10 @@ def get_client_address(request: Request) -> str | None:
402
402
  host = "localhost"
403
403
  return f"{proto}://{host}:{xfp}" if xfp else f"{proto}://{host}"
404
404
 
405
- host = request.client.host if request.client else ""
406
- port = request.client.port if request.client else None
407
- if host in ("127.0.0.1", "::1"):
408
- host = "localhost"
409
- if host and port:
410
- return f"{proto}://{host}:{port}"
411
- if host:
412
- return f"{proto}://{host}"
405
+ # Fallback: use Host header which contains the server address the client connected to
406
+ host_header = request.headers.get("host")
407
+ if host_header:
408
+ return f"{proto}://{host_header}"
413
409
  return None
414
410
  except Exception:
415
411
  return None
@@ -447,14 +443,10 @@ def get_client_address_socketio(environ: dict[str, Any]) -> str | None:
447
443
  host = "localhost"
448
444
  return f"{proto}://{host}:{xfp}" if xfp else f"{proto}://{host}"
449
445
 
450
- host = environ.get("REMOTE_ADDR", "")
451
- port = environ.get("REMOTE_PORT")
452
- if host in ("127.0.0.1", "::1"):
453
- host = "localhost"
454
- if host and port:
455
- return f"{proto}://{host}:{port}"
456
- if host:
457
- return f"{proto}://{host}"
446
+ # Fallback: use HTTP_HOST which contains the server address the client connected to
447
+ host_header = environ.get("HTTP_HOST")
448
+ if host_header:
449
+ return f"{proto}://{host_header}"
458
450
  return None
459
451
  except Exception:
460
452
  return None
@@ -21,19 +21,32 @@ logger = logging.getLogger(__name__)
21
21
  class ReactProxy:
22
22
  """
23
23
  Handles proxying HTTP requests and WebSocket connections to React Router server.
24
+
25
+ In single-server mode, the Python server proxies unmatched routes to the React
26
+ dev server. This proxy rewrites URLs in responses to use the external server
27
+ address instead of the internal React server address.
24
28
  """
25
29
 
26
30
  react_server_address: str
31
+ server_address: str
27
32
  _client: httpx.AsyncClient | None
28
33
 
29
- def __init__(self, react_server_address: str):
34
+ def __init__(self, react_server_address: str, server_address: str):
30
35
  """
31
36
  Args:
32
- react_server_address: React Router server full URL (required in single-server mode)
37
+ react_server_address: Internal React Router server URL (e.g., http://localhost:5173)
38
+ server_address: External server URL exposed to clients (e.g., http://localhost:8000)
33
39
  """
34
40
  self.react_server_address = react_server_address
41
+ self.server_address = server_address
35
42
  self._client = None
36
43
 
44
+ def _rewrite_url(self, url: str) -> str:
45
+ """Rewrite internal React server URLs to external server address."""
46
+ if self.react_server_address in url:
47
+ return url.replace(self.react_server_address, self.server_address)
48
+ return url
49
+
37
50
  @property
38
51
  def client(self) -> httpx.AsyncClient:
39
52
  """Lazy initialization of HTTP client."""
@@ -190,12 +203,12 @@ class ReactProxy:
190
203
  # Send request with streaming
191
204
  r = await self.client.send(req, stream=True)
192
205
 
193
- # Filter out headers that shouldn't be present in streaming responses
194
- response_headers = {
195
- k: v
196
- for k, v in r.headers.items()
197
- # if k.lower() not in ("content-length", "transfer-encoding")
198
- }
206
+ # Rewrite headers that may contain internal React server URLs
207
+ response_headers: dict[str, str] = {}
208
+ for k, v in r.headers.items():
209
+ if k.lower() in ("location", "content-location"):
210
+ v = self._rewrite_url(v)
211
+ response_headers[k] = v
199
212
 
200
213
  return StreamingResponse(
201
214
  r.aiter_raw(),