nextpy-framework 1.0.0__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 (49) hide show
  1. nextpy/__init__.py +50 -0
  2. nextpy/auth.py +94 -0
  3. nextpy/builder.py +123 -0
  4. nextpy/cli.py +490 -0
  5. nextpy/components/__init__.py +45 -0
  6. nextpy/components/feedback.py +210 -0
  7. nextpy/components/form.py +346 -0
  8. nextpy/components/head.py +167 -0
  9. nextpy/components/hooks_provider.py +64 -0
  10. nextpy/components/image.py +180 -0
  11. nextpy/components/layout.py +206 -0
  12. nextpy/components/link.py +132 -0
  13. nextpy/components/loader.py +65 -0
  14. nextpy/components/toast.py +101 -0
  15. nextpy/components/visual.py +185 -0
  16. nextpy/config.py +75 -0
  17. nextpy/core/__init__.py +21 -0
  18. nextpy/core/builder.py +237 -0
  19. nextpy/core/data_fetching.py +221 -0
  20. nextpy/core/renderer.py +252 -0
  21. nextpy/core/router.py +233 -0
  22. nextpy/core/sync.py +34 -0
  23. nextpy/db.py +121 -0
  24. nextpy/dev_server.py +69 -0
  25. nextpy/dev_tools.py +157 -0
  26. nextpy/errors.py +70 -0
  27. nextpy/hooks.py +348 -0
  28. nextpy/performance.py +78 -0
  29. nextpy/plugins.py +61 -0
  30. nextpy/py.typed +0 -0
  31. nextpy/server/__init__.py +6 -0
  32. nextpy/server/app.py +325 -0
  33. nextpy/server/debug.py +93 -0
  34. nextpy/server/middleware.py +88 -0
  35. nextpy/utils/__init__.py +0 -0
  36. nextpy/utils/cache.py +89 -0
  37. nextpy/utils/email.py +59 -0
  38. nextpy/utils/file_upload.py +65 -0
  39. nextpy/utils/logging.py +52 -0
  40. nextpy/utils/search.py +59 -0
  41. nextpy/utils/seo.py +85 -0
  42. nextpy/utils/validators.py +58 -0
  43. nextpy/websocket.py +76 -0
  44. nextpy_framework-1.0.0.dist-info/METADATA +343 -0
  45. nextpy_framework-1.0.0.dist-info/RECORD +49 -0
  46. nextpy_framework-1.0.0.dist-info/WHEEL +5 -0
  47. nextpy_framework-1.0.0.dist-info/entry_points.txt +2 -0
  48. nextpy_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
  49. nextpy_framework-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,167 @@
1
+ """
2
+ NextPy Head Component - SEO and meta tag management
3
+ Similar to Next.js's next/head
4
+ """
5
+
6
+ from typing import Any, Dict, List, Optional
7
+ from markupsafe import Markup
8
+
9
+
10
+ class Head:
11
+ """
12
+ Head component for managing document head elements
13
+
14
+ Usage in templates:
15
+ {{ Head(title="My Page", description="Page description") }}
16
+
17
+ Or in Python:
18
+ head = Head(title="My Page")
19
+ head.add_meta(name="author", content="John Doe")
20
+ print(head.render())
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ title: Optional[str] = None,
26
+ description: Optional[str] = None,
27
+ keywords: Optional[List[str]] = None,
28
+ canonical: Optional[str] = None,
29
+ og_title: Optional[str] = None,
30
+ og_description: Optional[str] = None,
31
+ og_image: Optional[str] = None,
32
+ og_type: str = "website",
33
+ twitter_card: str = "summary_large_image",
34
+ favicon: Optional[str] = None,
35
+ **kwargs: Any,
36
+ ):
37
+ self.title = title
38
+ self.description = description
39
+ self.keywords = keywords or []
40
+ self.canonical = canonical
41
+ self.og_title = og_title or title
42
+ self.og_description = og_description or description
43
+ self.og_image = og_image
44
+ self.og_type = og_type
45
+ self.twitter_card = twitter_card
46
+ self.favicon = favicon
47
+ self.extra_meta: List[Dict[str, str]] = []
48
+ self.extra_links: List[Dict[str, str]] = []
49
+ self.extra_scripts: List[Dict[str, str]] = []
50
+ self.extra_kwargs = kwargs
51
+
52
+ def add_meta(self, **attrs: str) -> "Head":
53
+ """Add a custom meta tag"""
54
+ self.extra_meta.append(attrs)
55
+ return self
56
+
57
+ def add_link(self, **attrs: str) -> "Head":
58
+ """Add a custom link tag"""
59
+ self.extra_links.append(attrs)
60
+ return self
61
+
62
+ def add_script(self, src: Optional[str] = None, **attrs: str) -> "Head":
63
+ """Add a script tag"""
64
+ if src:
65
+ attrs["src"] = src
66
+ self.extra_scripts.append(attrs)
67
+ return self
68
+
69
+ def render(self) -> str:
70
+ """Render the head elements as HTML"""
71
+ elements = []
72
+
73
+ if self.title:
74
+ elements.append(f"<title>{self._escape(self.title)}</title>")
75
+
76
+ elements.append('<meta charset="UTF-8">')
77
+ elements.append('<meta name="viewport" content="width=device-width, initial-scale=1.0">')
78
+
79
+ if self.description:
80
+ elements.append(
81
+ f'<meta name="description" content="{self._escape(self.description)}">'
82
+ )
83
+
84
+ if self.keywords:
85
+ keywords_str = ", ".join(self.keywords)
86
+ elements.append(f'<meta name="keywords" content="{self._escape(keywords_str)}">')
87
+
88
+ if self.canonical:
89
+ elements.append(f'<link rel="canonical" href="{self._escape(self.canonical)}">')
90
+
91
+ if self.og_title:
92
+ elements.append(f'<meta property="og:title" content="{self._escape(self.og_title)}">')
93
+ if self.og_description:
94
+ elements.append(
95
+ f'<meta property="og:description" content="{self._escape(self.og_description)}">'
96
+ )
97
+ if self.og_image:
98
+ elements.append(f'<meta property="og:image" content="{self._escape(self.og_image)}">')
99
+ if self.og_type:
100
+ elements.append(f'<meta property="og:type" content="{self._escape(self.og_type)}">')
101
+
102
+ if self.twitter_card:
103
+ elements.append(f'<meta name="twitter:card" content="{self._escape(self.twitter_card)}">')
104
+ if self.og_title:
105
+ elements.append(
106
+ f'<meta name="twitter:title" content="{self._escape(self.og_title)}">'
107
+ )
108
+ if self.og_description:
109
+ elements.append(
110
+ f'<meta name="twitter:description" content="{self._escape(self.og_description)}">'
111
+ )
112
+ if self.og_image:
113
+ elements.append(
114
+ f'<meta name="twitter:image" content="{self._escape(self.og_image)}">'
115
+ )
116
+
117
+ if self.favicon:
118
+ elements.append(f'<link rel="icon" href="{self._escape(self.favicon)}">')
119
+
120
+ for meta in self.extra_meta:
121
+ attrs_str = " ".join(f'{k}="{self._escape(v)}"' for k, v in meta.items())
122
+ elements.append(f"<meta {attrs_str}>")
123
+
124
+ for link in self.extra_links:
125
+ attrs_str = " ".join(f'{k}="{self._escape(v)}"' for k, v in link.items())
126
+ elements.append(f"<link {attrs_str}>")
127
+
128
+ for script in self.extra_scripts:
129
+ attrs_str = " ".join(f'{k}="{self._escape(v)}"' for k, v in script.items())
130
+ if "src" in script:
131
+ elements.append(f"<script {attrs_str}></script>")
132
+ else:
133
+ content = script.pop("content", "")
134
+ elements.append(f"<script {attrs_str}>{content}</script>")
135
+
136
+ return "\n ".join(elements)
137
+
138
+ def __html__(self) -> str:
139
+ """Make the component work with Jinja2's autoescape"""
140
+ return self.render()
141
+
142
+ def __str__(self) -> str:
143
+ return self.render()
144
+
145
+ def __call__(self, **kwargs: Any) -> Markup:
146
+ """Allow calling Head() in templates with additional args"""
147
+ for key, value in kwargs.items():
148
+ if hasattr(self, key):
149
+ setattr(self, key, value)
150
+ return Markup(self.render())
151
+
152
+ @staticmethod
153
+ def _escape(value: str) -> str:
154
+ """Escape HTML special characters"""
155
+ return (
156
+ str(value)
157
+ .replace("&", "&amp;")
158
+ .replace("<", "&lt;")
159
+ .replace(">", "&gt;")
160
+ .replace('"', "&quot;")
161
+ .replace("'", "&#x27;")
162
+ )
163
+
164
+
165
+ def create_head(**kwargs: Any) -> Head:
166
+ """Factory function to create a Head component"""
167
+ return Head(**kwargs)
@@ -0,0 +1,64 @@
1
+ """
2
+ Hooks Provider for SSR Integration
3
+ Enables hooks to work seamlessly in server-side rendered pages
4
+ """
5
+
6
+ from typing import Any, Dict, Optional
7
+ from dataclasses import dataclass
8
+
9
+
10
+ @dataclass
11
+ class HooksContext:
12
+ """Context for hooks across requests"""
13
+ component_id: str
14
+ state_data: Dict[str, Any]
15
+ request_id: Optional[str] = None
16
+
17
+
18
+ class HooksProvider:
19
+ """Global hooks provider for SSR"""
20
+
21
+ _instance = None
22
+ _request_contexts: Dict[str, HooksContext] = {}
23
+
24
+ def __new__(cls):
25
+ if cls._instance is None:
26
+ cls._instance = super().__new__(cls)
27
+ return cls._instance
28
+
29
+ @classmethod
30
+ def create_context(cls, component_id: str, request_id: Optional[str] = None) -> HooksContext:
31
+ """Create new hooks context for a request"""
32
+ context = HooksContext(
33
+ component_id=component_id,
34
+ state_data={},
35
+ request_id=request_id
36
+ )
37
+ if request_id:
38
+ cls._request_contexts[request_id] = context
39
+ return context
40
+
41
+ @classmethod
42
+ def get_context(cls, request_id: str) -> Optional[HooksContext]:
43
+ """Get hooks context for a request"""
44
+ return cls._request_contexts.get(request_id)
45
+
46
+ @classmethod
47
+ def cleanup_request(cls, request_id: str):
48
+ """Clean up context after request"""
49
+ if request_id in cls._request_contexts:
50
+ del cls._request_contexts[request_id]
51
+
52
+
53
+ def with_hooks(component_id: str):
54
+ """Decorator to enable hooks in page components"""
55
+ def decorator(func):
56
+ def wrapper(*args, **kwargs):
57
+ from nextpy.hooks import StateManager
58
+ StateManager.set_component(component_id)
59
+ try:
60
+ return func(*args, **kwargs)
61
+ finally:
62
+ StateManager.reset_hook_index()
63
+ return wrapper
64
+ return decorator
@@ -0,0 +1,180 @@
1
+ """
2
+ NextPy Image Component - Optimized image handling
3
+ Similar to Next.js's next/image with automatic optimization
4
+ """
5
+
6
+ from typing import Any, List, Optional, Tuple
7
+ from pathlib import Path
8
+ from markupsafe import Markup
9
+
10
+
11
+ class Image:
12
+ """
13
+ Optimized Image component with automatic sizing and lazy loading
14
+
15
+ Usage in templates:
16
+ {{ Image("/images/hero.jpg", alt="Hero image", width=800, height=600) }}
17
+
18
+ Features:
19
+ - Automatic srcset generation for responsive images
20
+ - Lazy loading by default
21
+ - Placeholder blur support
22
+ - Size optimization hints
23
+ """
24
+
25
+ DEFAULT_SIZES = [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
26
+ DEFAULT_QUALITY = 75
27
+
28
+ def __init__(
29
+ self,
30
+ src: str,
31
+ alt: str = "",
32
+ width: Optional[int] = None,
33
+ height: Optional[int] = None,
34
+ layout: str = "intrinsic",
35
+ priority: bool = False,
36
+ placeholder: str = "empty",
37
+ blur_data_url: Optional[str] = None,
38
+ quality: int = DEFAULT_QUALITY,
39
+ sizes: Optional[str] = None,
40
+ class_: Optional[str] = None,
41
+ style: Optional[str] = None,
42
+ **kwargs: Any,
43
+ ):
44
+ self.src = src
45
+ self.alt = alt
46
+ self.width = width
47
+ self.height = height
48
+ self.layout = layout
49
+ self.priority = priority
50
+ self.placeholder = placeholder
51
+ self.blur_data_url = blur_data_url
52
+ self.quality = quality
53
+ self.sizes = sizes
54
+ self.class_ = class_
55
+ self.style = style
56
+ self.extra_attrs = kwargs
57
+
58
+ def render(self) -> str:
59
+ """Render the image as an optimized HTML img tag"""
60
+ attrs = []
61
+
62
+ if self._is_external_url():
63
+ attrs.append(f'src="{self._escape(self.src)}"')
64
+ else:
65
+ attrs.append(f'src="{self._escape(self._get_optimized_url())}"')
66
+ srcset = self._generate_srcset()
67
+ if srcset:
68
+ attrs.append(f'srcset="{srcset}"')
69
+
70
+ attrs.append(f'alt="{self._escape(self.alt)}"')
71
+
72
+ if self.width:
73
+ attrs.append(f'width="{self.width}"')
74
+ if self.height:
75
+ attrs.append(f'height="{self.height}"')
76
+
77
+ if self.sizes:
78
+ attrs.append(f'sizes="{self._escape(self.sizes)}"')
79
+ elif self.width:
80
+ attrs.append(f'sizes="(max-width: {self.width}px) 100vw, {self.width}px"')
81
+
82
+ if not self.priority:
83
+ attrs.append('loading="lazy"')
84
+ attrs.append('decoding="async"')
85
+ else:
86
+ attrs.append('fetchpriority="high"')
87
+
88
+ if self.placeholder == "blur" and self.blur_data_url:
89
+ attrs.append(f'style="background-image: url({self.blur_data_url}); background-size: cover;"')
90
+ elif self.style:
91
+ attrs.append(f'style="{self._escape(self.style)}"')
92
+
93
+ if self.class_:
94
+ attrs.append(f'class="{self._escape(self.class_)}"')
95
+
96
+ for key, value in self.extra_attrs.items():
97
+ attr_name = key.replace("_", "-")
98
+ if value is True:
99
+ attrs.append(attr_name)
100
+ elif value is not False and value is not None:
101
+ attrs.append(f'{attr_name}="{self._escape(str(value))}"')
102
+
103
+ wrapper_style = self._get_wrapper_style()
104
+ img_tag = f"<img {' '.join(attrs)}>"
105
+
106
+ if wrapper_style:
107
+ return f'<span style="{wrapper_style}">{img_tag}</span>'
108
+ return img_tag
109
+
110
+ def _is_external_url(self) -> bool:
111
+ """Check if the src is an external URL"""
112
+ return self.src.startswith(("http://", "https://", "//"))
113
+
114
+ def _get_optimized_url(self) -> str:
115
+ """Get the optimized image URL"""
116
+ return f"/_nextpy/image?url={self.src}&w={self.width or 0}&q={self.quality}"
117
+
118
+ def _generate_srcset(self) -> str:
119
+ """Generate srcset for responsive images"""
120
+ if self._is_external_url():
121
+ return ""
122
+
123
+ srcset_parts = []
124
+ max_width = self.width or 1920
125
+
126
+ for size in self.DEFAULT_SIZES:
127
+ if size <= max_width * 2:
128
+ url = f"/_nextpy/image?url={self.src}&w={size}&q={self.quality}"
129
+ srcset_parts.append(f"{url} {size}w")
130
+
131
+ return ", ".join(srcset_parts)
132
+
133
+ def _get_wrapper_style(self) -> str:
134
+ """Get wrapper style based on layout mode"""
135
+ if self.layout == "fill":
136
+ return "display: block; overflow: hidden; position: absolute; inset: 0;"
137
+ elif self.layout == "responsive" and self.width and self.height:
138
+ aspect = (self.height / self.width) * 100
139
+ return f"display: block; overflow: hidden; position: relative; padding-bottom: {aspect:.2f}%;"
140
+ elif self.layout == "intrinsic" and self.width and self.height:
141
+ return f"display: inline-block; max-width: 100%; width: {self.width}px;"
142
+ return ""
143
+
144
+ def __html__(self) -> str:
145
+ """Make the component work with Jinja2's autoescape"""
146
+ return self.render()
147
+
148
+ def __str__(self) -> str:
149
+ return self.render()
150
+
151
+ def __call__(self, **kwargs: Any) -> Markup:
152
+ """Allow calling Image() in templates with arguments"""
153
+ for key, value in kwargs.items():
154
+ if hasattr(self, key):
155
+ setattr(self, key, value)
156
+ else:
157
+ self.extra_attrs[key] = value
158
+ return Markup(self.render())
159
+
160
+ @staticmethod
161
+ def _escape(value: str) -> str:
162
+ """Escape HTML special characters"""
163
+ return (
164
+ str(value)
165
+ .replace("&", "&amp;")
166
+ .replace("<", "&lt;")
167
+ .replace(">", "&gt;")
168
+ .replace('"', "&quot;")
169
+ .replace("'", "&#x27;")
170
+ )
171
+
172
+
173
+ def image(src: str, alt: str = "", **kwargs: Any) -> Markup:
174
+ """
175
+ Functional helper to create an Image
176
+
177
+ Usage in templates:
178
+ {{ image("/hero.jpg", "Hero", width=800) }}
179
+ """
180
+ return Markup(Image(src, alt, **kwargs).render())
@@ -0,0 +1,206 @@
1
+ """
2
+ Layout components for NextPy
3
+ Provides grid, flex, container, and other layout utilities
4
+ """
5
+
6
+ from typing import Optional, List, Dict, Any
7
+
8
+
9
+ def Container(
10
+ children: str = "",
11
+ max_width: str = "max-w-6xl",
12
+ padding: str = "px-4 sm:px-6 lg:px-8",
13
+ **kwargs
14
+ ) -> str:
15
+ """Container component with responsive max-width"""
16
+ classes = f"mx-auto {max_width} {padding}"
17
+ return f'<div class="{classes}">{children}</div>'
18
+
19
+
20
+ def Grid(
21
+ children: str = "",
22
+ columns: int = 3,
23
+ gap: str = "gap-6",
24
+ responsive: bool = True,
25
+ **kwargs
26
+ ) -> str:
27
+ """Grid layout component"""
28
+ if responsive:
29
+ cols = f"md:grid-cols-{columns}"
30
+ classes = f"grid {cols} {gap}"
31
+ else:
32
+ cols = f"grid-cols-{columns}"
33
+ classes = f"grid {cols} {gap}"
34
+
35
+ return f'<div class="{classes}">{children}</div>'
36
+
37
+
38
+ def Flex(
39
+ children: str = "",
40
+ direction: str = "row",
41
+ justify: str = "justify-start",
42
+ align: str = "items-start",
43
+ gap: str = "gap-4",
44
+ **kwargs
45
+ ) -> str:
46
+ """Flex layout component"""
47
+ flex_dir = "flex-col" if direction == "column" else "flex-row"
48
+ classes = f"flex {flex_dir} {justify} {align} {gap}"
49
+ return f'<div class="{classes}">{children}</div>'
50
+
51
+
52
+ def Stack(
53
+ children: str = "",
54
+ direction: str = "vertical",
55
+ spacing: str = "space-y-4",
56
+ **kwargs
57
+ ) -> str:
58
+ """Stack component (vertical or horizontal)"""
59
+ if direction == "horizontal":
60
+ spacing = spacing.replace("space-y", "space-x")
61
+ classes = f"flex flex-row {spacing}"
62
+ else:
63
+ classes = f"flex flex-col {spacing}"
64
+
65
+ return f'<div class="{classes}">{children}</div>'
66
+
67
+
68
+ def Section(
69
+ children: str = "",
70
+ title: Optional[str] = None,
71
+ bg_color: str = "bg-white",
72
+ padding: str = "py-16",
73
+ **kwargs
74
+ ) -> str:
75
+ """Section component with optional title"""
76
+ html = f'<section class="{bg_color} {padding}"><div class="max-w-6xl mx-auto px-4">'
77
+
78
+ if title:
79
+ html += f'<h2 class="text-3xl font-bold mb-8">{title}</h2>'
80
+
81
+ html += children + '</div></section>'
82
+ return html
83
+
84
+
85
+ def Row(
86
+ children: str = "",
87
+ gap: str = "gap-4",
88
+ **kwargs
89
+ ) -> str:
90
+ """Row component (horizontal flex)"""
91
+ return f'<div class="flex flex-row {gap}">{children}</div>'
92
+
93
+
94
+ def Column(
95
+ children: str = "",
96
+ gap: str = "gap-4",
97
+ **kwargs
98
+ ) -> str:
99
+ """Column component (vertical flex)"""
100
+ return f'<div class="flex flex-col {gap}">{children}</div>'
101
+
102
+
103
+ def AspectRatio(
104
+ children: str = "",
105
+ ratio: str = "16/9",
106
+ **kwargs
107
+ ) -> str:
108
+ """AspectRatio component"""
109
+ ratio_class = {
110
+ "1/1": "aspect-square",
111
+ "4/3": "aspect-video",
112
+ "16/9": "aspect-video",
113
+ "3/2": "aspect-video",
114
+ }.get(ratio, "aspect-video")
115
+
116
+ return f'<div class="{ratio_class} overflow-hidden">{children}</div>'
117
+
118
+
119
+ def Spacer(height: str = "h-4", **kwargs) -> str:
120
+ """Spacer component for vertical spacing"""
121
+ return f'<div class="{height}"></div>'
122
+
123
+
124
+ def Divider(
125
+ color: str = "border-gray-200",
126
+ **kwargs
127
+ ) -> str:
128
+ """Divider/separator component"""
129
+ return f'<hr class="border {color}">'
130
+
131
+
132
+ def Center(
133
+ children: str = "",
134
+ **kwargs
135
+ ) -> str:
136
+ """Center component"""
137
+ return f'<div class="flex items-center justify-center">{children}</div>'
138
+
139
+
140
+ def Sidebar(
141
+ children: str = "",
142
+ sidebar_content: str = "",
143
+ sidebar_width: str = "w-64",
144
+ **kwargs
145
+ ) -> str:
146
+ """Sidebar layout component"""
147
+ return f'''
148
+ <div class="flex gap-6">
149
+ <aside class="{sidebar_width} flex-shrink-0">
150
+ {sidebar_content}
151
+ </aside>
152
+ <main class="flex-1">
153
+ {children}
154
+ </main>
155
+ </div>
156
+ '''
157
+
158
+
159
+ def TwoColumn(
160
+ left: str = "",
161
+ right: str = "",
162
+ gap: str = "gap-8",
163
+ **kwargs
164
+ ) -> str:
165
+ """Two-column layout"""
166
+ return f'''
167
+ <div class="grid grid-cols-2 {gap}">
168
+ <div>{left}</div>
169
+ <div>{right}</div>
170
+ </div>
171
+ '''
172
+
173
+
174
+ def ThreeColumn(
175
+ left: str = "",
176
+ center: str = "",
177
+ right: str = "",
178
+ gap: str = "gap-6",
179
+ **kwargs
180
+ ) -> str:
181
+ """Three-column layout"""
182
+ return f'''
183
+ <div class="grid grid-cols-3 {gap}">
184
+ <div>{left}</div>
185
+ <div>{center}</div>
186
+ <div>{right}</div>
187
+ </div>
188
+ '''
189
+
190
+
191
+ __all__ = [
192
+ 'Container',
193
+ 'Grid',
194
+ 'Flex',
195
+ 'Stack',
196
+ 'Section',
197
+ 'Row',
198
+ 'Column',
199
+ 'AspectRatio',
200
+ 'Spacer',
201
+ 'Divider',
202
+ 'Center',
203
+ 'Sidebar',
204
+ 'TwoColumn',
205
+ 'ThreeColumn',
206
+ ]