nextpy-framework 2.4.8__tar.gz → 2.4.9__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 (81) hide show
  1. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/cli.py +1 -1
  2. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/component_renderer.py +4 -2
  3. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/jsx_preprocessor.py +5 -4
  4. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/true_jsx.py +58 -16
  5. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9/.nextpy_framework/nextpy_framework.egg-info}/PKG-INFO +1 -1
  6. {nextpy_framework-2.4.8/.nextpy_framework/nextpy_framework.egg-info → nextpy_framework-2.4.9}/PKG-INFO +1 -1
  7. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/pyproject.toml +1 -1
  8. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/__init__.py +0 -0
  9. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/auth.py +0 -0
  10. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/builder.py +0 -0
  11. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/__init__.py +0 -0
  12. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/debug/AutoDebug.py +0 -0
  13. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/debug/DebugIcon.py +0 -0
  14. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/debug/DebugIconFixed.py +0 -0
  15. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/feedback.py +0 -0
  16. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/form.py +0 -0
  17. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/head.py +0 -0
  18. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/hooks_provider.py +0 -0
  19. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/image.py +0 -0
  20. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/layout.py +0 -0
  21. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/link.py +0 -0
  22. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/loader.py +0 -0
  23. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/navigation.py +0 -0
  24. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/toast.py +0 -0
  25. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/ui.py +0 -0
  26. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components/visual.py +0 -0
  27. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/components.py +0 -0
  28. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/config.py +0 -0
  29. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/__init__.py +0 -0
  30. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/builder.py +0 -0
  31. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/component_router.py +0 -0
  32. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/data_fetching.py +0 -0
  33. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/demo_pages_simple.py +0 -0
  34. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/demo_router.py +0 -0
  35. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/renderer.py +0 -0
  36. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/router.py +0 -0
  37. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/core/sync.py +0 -0
  38. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/db.py +0 -0
  39. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/dev_server.py +0 -0
  40. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/dev_tools.py +0 -0
  41. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/errors.py +0 -0
  42. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/hooks.py +0 -0
  43. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/hooks_provider.py +0 -0
  44. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/hooks_provider_new.py +0 -0
  45. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/jsx.py +0 -0
  46. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/jsx_transformer.py +0 -0
  47. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/main.py +0 -0
  48. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/performance.py +0 -0
  49. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/plugins/__init__.py +0 -0
  50. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/plugins/base.py +0 -0
  51. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/plugins/builtin.py +0 -0
  52. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/plugins/config.py +0 -0
  53. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/plugins.py +0 -0
  54. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/py.typed +0 -0
  55. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/security.py +0 -0
  56. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/server/__init__.py +0 -0
  57. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/server/app.py +0 -0
  58. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/server/debug.py +0 -0
  59. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/server/middleware.py +0 -0
  60. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/utils/__init__.py +0 -0
  61. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/utils/cache.py +0 -0
  62. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/utils/email.py +0 -0
  63. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/utils/file_upload.py +0 -0
  64. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/utils/logging.py +0 -0
  65. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/utils/search.py +0 -0
  66. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/utils/seo.py +0 -0
  67. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/utils/validators.py +0 -0
  68. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy/websocket.py +0 -0
  69. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy_framework.egg-info/SOURCES.txt +0 -0
  70. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy_framework.egg-info/dependency_links.txt +0 -0
  71. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy_framework.egg-info/entry_points.txt +0 -0
  72. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy_framework.egg-info/requires.txt +0 -0
  73. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/.nextpy_framework/nextpy_framework.egg-info/top_level.txt +0 -0
  74. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/LICENSE +0 -0
  75. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/README.md +0 -0
  76. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/setup.cfg +0 -0
  77. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/tests/test_jsx_edgecases.py +0 -0
  78. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/tests/test_jsx_preprocessor.py +0 -0
  79. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/tests/test_routing.py +0 -0
  80. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/tests/test_server_features.py +0 -0
  81. {nextpy_framework-2.4.8 → nextpy_framework-2.4.9}/tests/test_tailwind_up_to_date.py +0 -0
@@ -2267,7 +2267,7 @@ watchdog>=3.0.0
2267
2267
  python-multipart>=0.0.6
2268
2268
  pillow>=10.0.0
2269
2269
  aiofiles>=23.0.0
2270
- httpx>=0.24.0
2270
+ httpx>=0.24.0,<0.25 # compatibility with Starlette TestClient
2271
2271
  sqlalchemy>=2.0.0
2272
2272
  python-dotenv>=1.0.0
2273
2273
  pyjwt>=2.8.0
@@ -219,8 +219,8 @@ class ComponentRenderer:
219
219
  # It's already a JSX element
220
220
  rendered = component
221
221
 
222
- # Convert to HTML
223
- html = render_jsx(rendered)
222
+ # Convert to HTML, passing page props as context for {expressions}
223
+ html = render_jsx(rendered, page_props)
224
224
 
225
225
  # Inject debug icon in development mode
226
226
  if AUTO_DEBUG_AVAILABLE and should_show_debug():
@@ -249,6 +249,7 @@ class ComponentRenderer:
249
249
  title = props.get('title', 'NextPy App')
250
250
  description = props.get('description', 'NextPy Application')
251
251
 
252
+ # Include local Tailwind stylesheet by default for created apps
252
253
  return f"""<!DOCTYPE html>
253
254
  <html lang="en">
254
255
  <head>
@@ -256,6 +257,7 @@ class ComponentRenderer:
256
257
  <meta name="viewport" content="width=device-width, initial-scale=1">
257
258
  <title>{title}</title>
258
259
  <meta name="description" content="{description}">
260
+ <link rel="stylesheet" href="/tailwind.css">
259
261
  <style>
260
262
  * {{ margin: 0; padding: 0; box-sizing: border-box; }}
261
263
  body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; }}
@@ -244,10 +244,11 @@ class JSXPreprocessor:
244
244
  file_path=file_path
245
245
  )
246
246
 
247
- # Instead of doing adhoc tag matching we can leverage the
248
- # shared parser; this will catch real syntax errors and avoid
249
- # false positives. The parser returns either a JSXElement or a
250
- # string, but will raise exceptions for malformed input.
247
+ # First perform balanced-tag checks to catch unclosed/mismatched tags
248
+ self._check_balanced_tags(jsx_content, file_path=file_path)
249
+ self._validate_jsx_structure(jsx_content, file_path=file_path)
250
+
251
+ # Then delegate to the shared parser for deeper validation
251
252
  try:
252
253
  from .true_jsx import parser
253
254
  parser.parse_jsx(jsx_content)
@@ -21,19 +21,43 @@ class JSXElement:
21
21
  """Convert to HTML string"""
22
22
  return self.to_html()
23
23
 
24
- def to_html(self) -> str:
25
- """Convert JSX element to HTML string"""
24
+ def to_html(self, context: Dict[str, Any] = None) -> str:
25
+ """Convert JSX element to HTML string.
26
+
27
+ Evaluates any {expressions} in text nodes and prop values using `context`.
28
+ """
26
29
  # Build props string
27
30
  props_str = ""
28
31
  if self.props:
29
32
  props_list = []
30
33
  for key, value in self.props.items():
34
+ # Map React-style `className` to HTML `class`
35
+ attr_name = 'class' if key == 'className' else key
36
+
37
+ # Boolean attribute
31
38
  if isinstance(value, bool) and value:
32
- props_list.append(key)
39
+ props_list.append(attr_name)
33
40
  elif value is not None and value != "":
41
+ # Skip event handlers
34
42
  if key.startswith("on_") and callable(value):
35
43
  continue
36
- props_list.append(f'{key}="{value}"')
44
+
45
+ # If value looks like a stored expression token '{expr}', evaluate at render time
46
+ if isinstance(value, str) and value.startswith('{') and value.endswith('}') and context is not None:
47
+ inner = value[1:-1].strip()
48
+ try:
49
+ evaluated = str(eval(inner, {}, context))
50
+ except Exception:
51
+ evaluated = ''
52
+ props_list.append(f'{attr_name}="{evaluated}"')
53
+ # If string contains inline {expr} parts, evaluate those
54
+ elif isinstance(value, str) and '{' in value and '}' in value and context is not None:
55
+ from re import sub
56
+ evaluated = _evaluate_expressions_in_string(value, context)
57
+ props_list.append(f'{attr_name}="{evaluated}"')
58
+ else:
59
+ props_list.append(f'{attr_name}="{value}"')
60
+
37
61
  props_str = " " + " ".join(props_list) if props_list else ""
38
62
 
39
63
  # Build children string
@@ -42,9 +66,13 @@ class JSXElement:
42
66
  children_parts = []
43
67
  for child in self.children:
44
68
  if isinstance(child, JSXElement):
45
- children_parts.append(child.to_html())
69
+ children_parts.append(child.to_html(context))
46
70
  else:
47
- children_parts.append(str(child))
71
+ # Evaluate any {expressions} inside text nodes
72
+ if isinstance(child, str) and '{' in child and '}' in child and context is not None:
73
+ children_parts.append(_evaluate_expressions_in_string(child, context))
74
+ else:
75
+ children_parts.append(str(child))
48
76
  children_str = "".join(children_parts)
49
77
 
50
78
  # Handle self-closing tags
@@ -52,10 +80,10 @@ class JSXElement:
52
80
  'img', 'br', 'hr', 'input', 'meta', 'link', 'area', 'base', 'col',
53
81
  'embed', 'source', 'track', 'wbr', 'command', 'keygen', 'menuitem', 'param'
54
82
  }
55
-
83
+
56
84
  if self.tag in self_closing_tags and not children_str:
57
85
  return f"<{self.tag}{props_str} />"
58
-
86
+
59
87
  return f"<{self.tag}{props_str}>{children_str}</{self.tag}>"
60
88
 
61
89
 
@@ -79,11 +107,8 @@ class JSXParser:
79
107
  if groups[0] and groups[1]: # {prop} syntax
80
108
  prop_name = groups[0]
81
109
  prop_value = groups[1].strip()
82
- # Try to evaluate as Python expression
83
- try:
84
- props[prop_name] = eval(prop_value)
85
- except:
86
- props[prop_name] = prop_value
110
+ # Store as a raw expression token to be evaluated at render time
111
+ props[prop_name] = '{' + prop_value + '}'
87
112
  elif groups[2] and groups[3]: # "prop" syntax
88
113
  props[groups[2]] = groups[3]
89
114
  elif groups[4] and groups[5]: # 'prop' syntax
@@ -180,11 +205,28 @@ def jsx(jsx_str: str) -> JSXElement:
180
205
  return parser.parse_jsx(jsx_str)
181
206
 
182
207
 
183
- def render_jsx(element) -> str:
184
- """Render JSX element to HTML string"""
208
+ def _evaluate_expressions_in_string(s: str, context: Dict[str, Any]) -> str:
209
+ """Find {expressions} in string `s` and evaluate them using `context`."""
210
+ def repl(match):
211
+ expr = match.group(1).strip()
212
+ try:
213
+ return str(eval(expr, {}, context or {}))
214
+ except Exception:
215
+ return ''
216
+
217
+ return re.sub(r'\{([^}]+)\}', repl, s)
218
+
219
+
220
+ def render_jsx(element, context: Dict[str, Any] = None) -> str:
221
+ """Render JSX element to HTML string, evaluating {expressions} with `context`.
222
+
223
+ Usage: `render_jsx(element, context)`
224
+ """
185
225
  if isinstance(element, JSXElement):
186
- return element.to_html()
226
+ return element.to_html(context)
187
227
  elif isinstance(element, str):
228
+ if context and '{' in element and '}' in element:
229
+ return _evaluate_expressions_in_string(element, context)
188
230
  return element
189
231
  else:
190
232
  return str(element)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextpy-framework
3
- Version: 2.4.8
3
+ Version: 2.4.9
4
4
  Summary: A Python web framework inspired by Next.js with file-based routing, SSR, and SSG and more
5
5
  Author: NextPy Team
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextpy-framework
3
- Version: 2.4.8
3
+ Version: 2.4.9
4
4
  Summary: A Python web framework inspired by Next.js with file-based routing, SSR, and SSG and more
5
5
  Author: NextPy Team
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nextpy-framework"
7
- version = "2.4.8"
7
+ version = "2.4.9"
8
8
  description = "A Python web framework inspired by Next.js with file-based routing, SSR, and SSG and more"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"