pulse-framework 0.1.49__tar.gz → 0.1.50__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.49 → pulse_framework-0.1.50}/PKG-INFO +1 -1
  2. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/pyproject.toml +1 -1
  3. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/hooks/runtime.py +4 -2
  4. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/messages.py +1 -0
  5. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/render_session.py +9 -2
  6. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/vdom.py +9 -11
  7. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/README.md +0 -0
  8. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/__init__.py +0 -0
  9. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/app.py +0 -0
  10. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/channel.py +0 -0
  11. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/__init__.py +0 -0
  12. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/cmd.py +0 -0
  13. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/dependencies.py +0 -0
  14. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/folder_lock.py +0 -0
  15. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/helpers.py +0 -0
  16. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/models.py +0 -0
  17. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/packages.py +0 -0
  18. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/processes.py +0 -0
  19. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/secrets.py +0 -0
  20. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cli/uvicorn_log_config.py +0 -0
  21. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/codegen/__init__.py +0 -0
  22. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/codegen/codegen.py +0 -0
  23. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/codegen/js.py +0 -0
  24. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/codegen/templates/__init__.py +0 -0
  25. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/codegen/templates/layout.py +0 -0
  26. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/codegen/templates/route.py +0 -0
  27. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/codegen/templates/routes_ts.py +0 -0
  28. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/codegen/utils.py +0 -0
  29. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/components/__init__.py +0 -0
  30. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/components/for_.py +0 -0
  31. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/components/if_.py +0 -0
  32. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/components/react_router.py +0 -0
  33. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/context.py +0 -0
  34. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/cookies.py +0 -0
  35. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/decorators.py +0 -0
  36. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/env.py +0 -0
  37. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/form.py +0 -0
  38. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/helpers.py +0 -0
  39. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/hooks/__init__.py +0 -0
  40. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/hooks/core.py +0 -0
  41. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/hooks/effects.py +0 -0
  42. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/hooks/init.py +0 -0
  43. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/hooks/setup.py +0 -0
  44. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/hooks/stable.py +0 -0
  45. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/hooks/states.py +0 -0
  46. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/html/__init__.py +0 -0
  47. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/html/elements.py +0 -0
  48. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/html/events.py +0 -0
  49. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/html/props.py +0 -0
  50. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/html/svg.py +0 -0
  51. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/html/tags.py +0 -0
  52. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/html/tags.pyi +0 -0
  53. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/__init__.py +0 -0
  54. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/__init__.pyi +0 -0
  55. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/_types.py +0 -0
  56. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/array.py +0 -0
  57. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/console.py +0 -0
  58. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/date.py +0 -0
  59. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/document.py +0 -0
  60. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/error.py +0 -0
  61. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/json.py +0 -0
  62. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/map.py +0 -0
  63. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/math.py +0 -0
  64. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/navigator.py +0 -0
  65. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/number.py +0 -0
  66. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/object.py +0 -0
  67. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/promise.py +0 -0
  68. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/regexp.py +0 -0
  69. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/set.py +0 -0
  70. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/string.py +0 -0
  71. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/weakmap.py +0 -0
  72. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/weakset.py +0 -0
  73. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/js/window.py +0 -0
  74. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/middleware.py +0 -0
  75. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/plugin.py +0 -0
  76. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/proxy.py +0 -0
  77. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/py.typed +0 -0
  78. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/__init__.py +0 -0
  79. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/client.py +0 -0
  80. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/common.py +0 -0
  81. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/effect.py +0 -0
  82. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/infinite_query.py +0 -0
  83. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/mutation.py +0 -0
  84. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/protocol.py +0 -0
  85. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/query.py +0 -0
  86. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/queries/store.py +0 -0
  87. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/react_component.py +0 -0
  88. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/reactive.py +0 -0
  89. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/reactive_extensions.py +0 -0
  90. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/renderer.py +0 -0
  91. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/request.py +0 -0
  92. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/routing.py +0 -0
  93. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/serializer.py +0 -0
  94. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/state.py +0 -0
  95. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/__init__.py +0 -0
  96. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/builtins.py +0 -0
  97. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/constants.py +0 -0
  98. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/context.py +0 -0
  99. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/errors.py +0 -0
  100. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/function.py +0 -0
  101. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/ids.py +0 -0
  102. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/imports.py +0 -0
  103. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/js_module.py +0 -0
  104. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/modules/__init__.py +0 -0
  105. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/modules/asyncio.py +0 -0
  106. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/modules/json.py +0 -0
  107. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/modules/math.py +0 -0
  108. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/modules/re.py +0 -0
  109. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/modules/tags.py +0 -0
  110. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/modules/typing.py +0 -0
  111. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/nodes.py +0 -0
  112. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/py_module.py +0 -0
  113. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/transpiler.py +0 -0
  114. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/transpiler/utils.py +0 -0
  115. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/types/__init__.py +0 -0
  116. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/types/event_handler.py +0 -0
  117. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/src/pulse/user_session.py +0 -0
  118. {pulse_framework-0.1.49 → pulse_framework-0.1.50}/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.49
3
+ Version: 0.1.50
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.49"
3
+ version = "0.1.50"
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"
@@ -103,11 +103,13 @@ async def set_cookie(
103
103
  )
104
104
 
105
105
 
106
- def navigate(path: str, *, replace: bool = False) -> None:
106
+ def navigate(path: str, *, replace: bool = False, hard: bool = False) -> None:
107
107
  ctx = PulseContext.get()
108
108
  if ctx.render is None:
109
109
  raise RuntimeError("navigate() must be invoked inside a Pulse callback context")
110
- ctx.render.send({"type": "navigate_to", "path": path, "replace": replace})
110
+ ctx.render.send(
111
+ {"type": "navigate_to", "path": path, "replace": replace, "hard": hard}
112
+ )
111
113
 
112
114
 
113
115
  def redirect(path: str, *, replace: bool = False) -> NoReturn:
@@ -48,6 +48,7 @@ class ServerNavigateToMessage(TypedDict):
48
48
  type: Literal["navigate_to"]
49
49
  path: str
50
50
  replace: bool
51
+ hard: bool
51
52
 
52
53
 
53
54
  class ServerApiCallMessage(TypedDict):
@@ -440,6 +440,7 @@ class RenderSession:
440
440
  type="navigate_to",
441
441
  path=msg["path"],
442
442
  replace=msg["replace"],
443
+ hard=msg.get("hard", False),
443
444
  )
444
445
 
445
446
  prev_sender = self._send_message
@@ -563,14 +564,20 @@ class RenderSession:
563
564
  # Prefer client-side navigation over emitting VDOM operations
564
565
  self.send(
565
566
  ServerNavigateToMessage(
566
- type="navigate_to", path=r.path, replace=r.replace
567
+ type="navigate_to",
568
+ path=r.path,
569
+ replace=r.replace,
570
+ hard=False,
567
571
  )
568
572
  )
569
573
  except NotFoundInterrupt:
570
574
  # Use app-configured not-found path; fallback to '/404'
571
575
  self.send(
572
576
  ServerNavigateToMessage(
573
- type="navigate_to", path=ctx.app.not_found, replace=True
577
+ type="navigate_to",
578
+ path=ctx.app.not_found,
579
+ replace=True,
580
+ hard=False,
574
581
  )
575
582
  )
576
583
 
@@ -265,7 +265,7 @@ class Component(Generic[P]):
265
265
  if key is not None and not isinstance(key, str):
266
266
  raise ValueError("key must be a string or None")
267
267
 
268
- # Flatten children if component takes children (has *children parameter)
268
+ # Flatten children if component accepts them via `*children` parameter
269
269
  if self._takes_children and args:
270
270
  flattened = _flatten_children(
271
271
  args, # pyright: ignore[reportArgumentType]
@@ -356,11 +356,11 @@ class ComponentNode:
356
356
  )
357
357
  if not isinstance(children_arg, tuple):
358
358
  children_arg = (children_arg,)
359
- # Flatten children for ComponentNode as well
359
+ # Flatten children when component accepts them via `*children` parameter
360
360
  flattened_children = _flatten_children(
361
361
  children_arg, parent_name=f"<{self.name}>", warn_stacklevel=4
362
362
  )
363
- result = ComponentNode(
363
+ return ComponentNode(
364
364
  fn=self.fn,
365
365
  args=tuple(flattened_children),
366
366
  kwargs=self.kwargs,
@@ -368,7 +368,6 @@ class ComponentNode:
368
368
  key=self.key,
369
369
  takes_children=self.takes_children,
370
370
  )
371
- return result
372
371
 
373
372
  @override
374
373
  def __repr__(self) -> str:
@@ -606,19 +605,18 @@ def _callable_qualname(fn: Callable[..., Any]) -> str:
606
605
 
607
606
 
608
607
  def _takes_children(fn: Callable[..., Any]) -> bool:
609
- # Lightweight check: children allowed if function accepts positional
610
- # arguments
608
+ """Return True if function accepts children via `*children` parameter.
609
+
610
+ Convention: A component accepts children if and only if it has a VAR_POSITIONAL
611
+ parameter named "children". This convention should be documented in user-facing docs.
612
+ """
611
613
  try:
612
614
  sig = signature(fn)
613
615
  except (ValueError, TypeError):
614
616
  # Builtins or callables without inspectable signature: assume no children
615
617
  return False
616
618
  for p in sig.parameters.values():
617
- if p.kind in (
618
- Parameter.VAR_POSITIONAL,
619
- Parameter.POSITIONAL_ONLY,
620
- Parameter.POSITIONAL_OR_KEYWORD,
621
- ):
619
+ if p.kind is Parameter.VAR_POSITIONAL and p.name == "children":
622
620
  return True
623
621
  return False
624
622