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,210 @@
1
+ """
2
+ Feedback components for NextPy
3
+ Alert, Toast, Progress, Spinner, Badge, etc.
4
+ """
5
+
6
+ from typing import Optional
7
+
8
+
9
+ def Alert(
10
+ message: str = "",
11
+ type: str = "info",
12
+ title: Optional[str] = None,
13
+ dismissible: bool = False,
14
+ **kwargs
15
+ ) -> str:
16
+ """Alert component"""
17
+ colors = {
18
+ "info": "bg-blue-50 border-l-4 border-blue-600 text-blue-900",
19
+ "success": "bg-green-50 border-l-4 border-green-600 text-green-900",
20
+ "warning": "bg-yellow-50 border-l-4 border-yellow-600 text-yellow-900",
21
+ "error": "bg-red-50 border-l-4 border-red-600 text-red-900",
22
+ }
23
+
24
+ color_class = colors.get(type, colors["info"])
25
+ close_btn = '<button class="ml-auto">×</button>' if dismissible else ""
26
+ title_html = f'<h3 class="font-bold mb-1">{title}</h3>' if title else ""
27
+
28
+ return f'''<div class="p-4 rounded-lg {color_class} flex items-start gap-3">
29
+ {title_html}
30
+ <p>{message}</p>
31
+ {close_btn}
32
+ </div>'''
33
+
34
+
35
+ def Badge(
36
+ text: str = "",
37
+ color: str = "blue",
38
+ size: str = "md",
39
+ **kwargs
40
+ ) -> str:
41
+ """Badge component"""
42
+ sizes = {
43
+ "sm": "px-2 py-1 text-xs",
44
+ "md": "px-3 py-1 text-sm",
45
+ "lg": "px-4 py-2 text-base",
46
+ }
47
+
48
+ colors_class = {
49
+ "blue": "bg-blue-100 text-blue-800",
50
+ "red": "bg-red-100 text-red-800",
51
+ "green": "bg-green-100 text-green-800",
52
+ "yellow": "bg-yellow-100 text-yellow-800",
53
+ "purple": "bg-purple-100 text-purple-800",
54
+ "gray": "bg-gray-100 text-gray-800",
55
+ }
56
+
57
+ size_class = sizes.get(size, sizes["md"])
58
+ color_class = colors_class.get(color, colors_class["blue"])
59
+
60
+ return f'<span class="inline-block rounded-full {size_class} {color_class}">{text}</span>'
61
+
62
+
63
+ def Progress(
64
+ value: int = 0,
65
+ max: int = 100,
66
+ show_label: bool = True,
67
+ **kwargs
68
+ ) -> str:
69
+ """Progress bar component"""
70
+ percentage = (value / max) * 100
71
+ label_html = f'<span class="text-xs font-semibold">{percentage:.0f}%</span>' if show_label else ""
72
+
73
+ return f'''<div class="flex items-center gap-2">
74
+ <div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
75
+ <div class="h-full bg-blue-600" style="width: {percentage}%"></div>
76
+ </div>
77
+ {label_html}
78
+ </div>'''
79
+
80
+
81
+ def Spinner(
82
+ size: str = "md",
83
+ color: str = "blue",
84
+ **kwargs
85
+ ) -> str:
86
+ """Spinner/loading component"""
87
+ sizes = {
88
+ "sm": "w-4 h-4",
89
+ "md": "w-8 h-8",
90
+ "lg": "w-12 h-12",
91
+ }
92
+
93
+ colors_class = {
94
+ "blue": "text-blue-600",
95
+ "red": "text-red-600",
96
+ "green": "text-green-600",
97
+ "gray": "text-gray-600",
98
+ }
99
+
100
+ size_class = sizes.get(size, sizes["md"])
101
+ color_class = colors_class.get(color, colors_class["blue"])
102
+
103
+ return f'''<svg class="animate-spin {size_class} {color_class}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
104
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
105
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
106
+ </svg>'''
107
+
108
+
109
+ def Skeleton(
110
+ width: str = "w-full",
111
+ height: str = "h-4",
112
+ count: int = 1,
113
+ **kwargs
114
+ ) -> str:
115
+ """Skeleton loader component"""
116
+ skeletons = ""
117
+ for _ in range(count):
118
+ skeletons += f'<div class="bg-gray-200 rounded animate-pulse {width} {height} mb-2"></div>'
119
+
120
+ return skeletons
121
+
122
+
123
+ def Toast(
124
+ message: str = "",
125
+ type: str = "info",
126
+ duration: int = 3000,
127
+ **kwargs
128
+ ) -> str:
129
+ """Toast notification component"""
130
+ colors = {
131
+ "info": "bg-blue-600",
132
+ "success": "bg-green-600",
133
+ "warning": "bg-yellow-600",
134
+ "error": "bg-red-600",
135
+ }
136
+
137
+ color_class = colors.get(type, colors["info"])
138
+
139
+ return f'''<div class="fixed bottom-4 right-4 {color_class} text-white px-6 py-3 rounded-lg shadow-lg animate-fade-in-up" role="alert">
140
+ {message}
141
+ </div>
142
+ <style>
143
+ @keyframes fadeInUp {{
144
+ from {{ opacity: 0; transform: translateY(20px); }}
145
+ to {{ opacity: 1; transform: translateY(0); }}
146
+ }}
147
+ .animate-fade-in-up {{ animation: fadeInUp 0.3s ease-in-out; }}
148
+ </style>'''
149
+
150
+
151
+ def Tooltip(
152
+ text: str = "",
153
+ content: str = "",
154
+ position: str = "top",
155
+ **kwargs
156
+ ) -> str:
157
+ """Tooltip component"""
158
+ positions = {
159
+ "top": "bottom-full mb-2",
160
+ "bottom": "top-full mt-2",
161
+ "left": "right-full mr-2",
162
+ "right": "left-full ml-2",
163
+ }
164
+
165
+ pos_class = positions.get(position, positions["top"])
166
+
167
+ return f'''<div class="relative inline-block group">
168
+ <span class="cursor-help underline decoration-dotted">{text}</span>
169
+ <div class="absolute {pos_class} opacity-0 group-hover:opacity-100 transition-opacity bg-gray-900 text-white px-3 py-2 rounded text-sm whitespace-nowrap pointer-events-none">
170
+ {content}
171
+ </div>
172
+ </div>'''
173
+
174
+
175
+ def Loader(
176
+ text: str = "Loading...",
177
+ **kwargs
178
+ ) -> str:
179
+ """Loader component with text"""
180
+ return f'''<div class="flex flex-col items-center justify-center gap-4">
181
+ {Spinner(size="lg")}
182
+ <p class="text-gray-600 font-semibold">{text}</p>
183
+ </div>'''
184
+
185
+
186
+ def Empty(
187
+ icon: str = "📭",
188
+ title: str = "No data",
189
+ message: str = "Nothing to display",
190
+ **kwargs
191
+ ) -> str:
192
+ """Empty state component"""
193
+ return f'''<div class="flex flex-col items-center justify-center py-16 text-center">
194
+ <div class="text-5xl mb-4">{icon}</div>
195
+ <h3 class="text-xl font-bold text-gray-900 mb-2">{title}</h3>
196
+ <p class="text-gray-600">{message}</p>
197
+ </div>'''
198
+
199
+
200
+ __all__ = [
201
+ 'Alert',
202
+ 'Badge',
203
+ 'Progress',
204
+ 'Spinner',
205
+ 'Skeleton',
206
+ 'Toast',
207
+ 'Tooltip',
208
+ 'Loader',
209
+ 'Empty',
210
+ ]
@@ -0,0 +1,346 @@
1
+ """
2
+ Form components for NextPy
3
+ Input, TextArea, Select, Checkbox, Radio, Form, etc.
4
+ """
5
+
6
+ from typing import Optional, List, Dict, Any
7
+
8
+
9
+ def Input(
10
+ name: str = "",
11
+ type: str = "text",
12
+ placeholder: str = "",
13
+ value: str = "",
14
+ required: bool = False,
15
+ disabled: bool = False,
16
+ class_name: str = "",
17
+ **kwargs
18
+ ) -> str:
19
+ """Input component"""
20
+ req = "required" if required else ""
21
+ dis = "disabled" if disabled else ""
22
+ default_class = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
23
+
24
+ return f'''<input
25
+ type="{type}"
26
+ name="{name}"
27
+ placeholder="{placeholder}"
28
+ value="{value}"
29
+ class="{class_name or default_class}"
30
+ {req}
31
+ {dis}
32
+ >'''
33
+
34
+
35
+ def TextArea(
36
+ name: str = "",
37
+ placeholder: str = "",
38
+ value: str = "",
39
+ rows: int = 4,
40
+ required: bool = False,
41
+ disabled: bool = False,
42
+ class_name: str = "",
43
+ **kwargs
44
+ ) -> str:
45
+ """TextArea component"""
46
+ req = "required" if required else ""
47
+ dis = "disabled" if disabled else ""
48
+ default_class = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
49
+
50
+ return f'''<textarea
51
+ name="{name}"
52
+ placeholder="{placeholder}"
53
+ rows="{rows}"
54
+ class="{class_name or default_class}"
55
+ {req}
56
+ {dis}
57
+ >{value}</textarea>'''
58
+
59
+
60
+ def Select(
61
+ name: str = "",
62
+ options: Optional[List[Dict[str, str]]] = None,
63
+ value: str = "",
64
+ required: bool = False,
65
+ disabled: bool = False,
66
+ class_name: str = "",
67
+ **kwargs
68
+ ) -> str:
69
+ """Select dropdown component"""
70
+ if options is None:
71
+ options = []
72
+
73
+ req = "required" if required else ""
74
+ dis = "disabled" if disabled else ""
75
+ default_class = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
76
+
77
+ option_html = ""
78
+ for opt in options:
79
+ selected = "selected" if opt.get("value") == value else ""
80
+ option_html += f'<option value="{opt.get("value")}" {selected}>{opt.get("label")}</option>'
81
+
82
+ return f'''<select
83
+ name="{name}"
84
+ class="{class_name or default_class}"
85
+ {req}
86
+ {dis}
87
+ >{option_html}</select>'''
88
+
89
+
90
+ def Checkbox(
91
+ name: str = "",
92
+ label: str = "",
93
+ checked: bool = False,
94
+ required: bool = False,
95
+ disabled: bool = False,
96
+ **kwargs
97
+ ) -> str:
98
+ """Checkbox component"""
99
+ check = "checked" if checked else ""
100
+ dis = "disabled" if disabled else ""
101
+ req = "required" if required else ""
102
+
103
+ return f'''<label class="flex items-center gap-2">
104
+ <input
105
+ type="checkbox"
106
+ name="{name}"
107
+ class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500"
108
+ {check}
109
+ {req}
110
+ {dis}
111
+ >
112
+ <span>{label}</span>
113
+ </label>'''
114
+
115
+
116
+ def Radio(
117
+ name: str = "",
118
+ label: str = "",
119
+ value: str = "",
120
+ checked: bool = False,
121
+ required: bool = False,
122
+ disabled: bool = False,
123
+ **kwargs
124
+ ) -> str:
125
+ """Radio button component"""
126
+ check = "checked" if checked else ""
127
+ dis = "disabled" if disabled else ""
128
+ req = "required" if required else ""
129
+
130
+ return f'''<label class="flex items-center gap-2">
131
+ <input
132
+ type="radio"
133
+ name="{name}"
134
+ value="{value}"
135
+ class="w-4 h-4 text-blue-600 border-gray-300 focus:ring-2 focus:ring-blue-500"
136
+ {check}
137
+ {req}
138
+ {dis}
139
+ >
140
+ <span>{label}</span>
141
+ </label>'''
142
+
143
+
144
+ def RadioGroup(
145
+ name: str = "",
146
+ options: Optional[List[Dict[str, str]]] = None,
147
+ value: str = "",
148
+ **kwargs
149
+ ) -> str:
150
+ """Radio group component"""
151
+ if options is None:
152
+ options = []
153
+
154
+ html = f'<fieldset class="space-y-3">'
155
+
156
+ for opt in options:
157
+ checked = opt.get("value") == value
158
+ label = opt.get("label", "")
159
+ opt_value = opt.get("value", "")
160
+ html += Radio(
161
+ name=name,
162
+ label=label,
163
+ value=opt_value,
164
+ checked=checked
165
+ )
166
+
167
+ html += '</fieldset>'
168
+ return html
169
+
170
+
171
+ def Form(
172
+ children: str = "",
173
+ action: str = "",
174
+ method: str = "POST",
175
+ onsubmit: str = "",
176
+ class_name: str = "",
177
+ **kwargs
178
+ ) -> str:
179
+ """Form component"""
180
+ submit_handler = f'onsubmit="{onsubmit}"' if onsubmit else ""
181
+ default_class = "space-y-6"
182
+
183
+ return f'''<form
184
+ action="{action}"
185
+ method="{method}"
186
+ class="{class_name or default_class}"
187
+ {submit_handler}
188
+ >{children}</form>'''
189
+
190
+
191
+ def FormGroup(
192
+ label: str = "",
193
+ children: str = "",
194
+ error: str = "",
195
+ **kwargs
196
+ ) -> str:
197
+ """Form group component (label + input)"""
198
+ error_html = f'<p class="text-red-600 text-sm mt-1">{error}</p>' if error else ""
199
+
200
+ return f'''<div class="space-y-2">
201
+ {f'<label class="block text-sm font-semibold text-gray-900">{label}</label>' if label else ''}
202
+ {children}
203
+ {error_html}
204
+ </div>'''
205
+
206
+
207
+ def FileInput(
208
+ name: str = "",
209
+ accept: str = "",
210
+ multiple: bool = False,
211
+ required: bool = False,
212
+ disabled: bool = False,
213
+ **kwargs
214
+ ) -> str:
215
+ """File input component"""
216
+ req = "required" if required else ""
217
+ dis = "disabled" if disabled else ""
218
+ mult = "multiple" if multiple else ""
219
+
220
+ return f'''<input
221
+ type="file"
222
+ name="{name}"
223
+ accept="{accept}"
224
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
225
+ {mult}
226
+ {req}
227
+ {dis}
228
+ >'''
229
+
230
+
231
+ def DateInput(
232
+ name: str = "",
233
+ label: str = "",
234
+ value: str = "",
235
+ required: bool = False,
236
+ **kwargs
237
+ ) -> str:
238
+ """Date input component"""
239
+ req = "required" if required else ""
240
+ label_html = f'<label class="block text-sm font-medium mb-1">{label}</label>' if label else ''
241
+ return f'''<div class="mb-4">
242
+ {label_html}
243
+ <input type="date" name="{name}" value="{value}" {req}
244
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"/>
245
+ </div>'''
246
+
247
+
248
+ def TimeInput(
249
+ name: str = "",
250
+ label: str = "",
251
+ value: str = "",
252
+ required: bool = False,
253
+ **kwargs
254
+ ) -> str:
255
+ """Time input component"""
256
+ req = "required" if required else ""
257
+ label_html = f'<label class="block text-sm font-medium mb-1">{label}</label>' if label else ''
258
+ return f'''<div class="mb-4">
259
+ {label_html}
260
+ <input type="time" name="{name}" value="{value}" {req}
261
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"/>
262
+ </div>'''
263
+
264
+
265
+ def PasswordInput(
266
+ name: str = "",
267
+ label: str = "",
268
+ placeholder: str = "",
269
+ required: bool = False,
270
+ **kwargs
271
+ ) -> str:
272
+ """Password input component"""
273
+ req = "required" if required else ""
274
+ label_html = f'<label class="block text-sm font-medium mb-1">{label}</label>' if label else ''
275
+ return f'''<div class="mb-4">
276
+ {label_html}
277
+ <input type="password" name="{name}" placeholder="{placeholder}" {req}
278
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"/>
279
+ </div>'''
280
+
281
+
282
+ def RangeInput(
283
+ name: str = "",
284
+ label: str = "",
285
+ value: int = 50,
286
+ min: int = 0,
287
+ max: int = 100,
288
+ **kwargs
289
+ ) -> str:
290
+ """Range slider component"""
291
+ return f'''<div class="mb-4">
292
+ {f'<label class="block text-sm font-medium mb-1">{label} (<span id="{name}_value">{value}</span>)</label>' if label else ''}
293
+ <input type="range" name="{name}" value="{value}" min="{min}" max="{max}"
294
+ onchange="document.getElementById('{name}_value').textContent = this.value"
295
+ class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"/>
296
+ </div>'''
297
+
298
+
299
+ def ColorInput(
300
+ name: str = "",
301
+ label: str = "",
302
+ value: str = "#3b82f6",
303
+ **kwargs
304
+ ) -> str:
305
+ """Color picker component"""
306
+ return f'''<div class="mb-4">
307
+ {f'<label class="block text-sm font-medium mb-1">{label}</label>' if label else ''}
308
+ <input type="color" name="{name}" value="{value}"
309
+ class="w-16 h-10 border border-gray-300 rounded-lg cursor-pointer"/>
310
+ </div>'''
311
+
312
+
313
+ def NumberInput(
314
+ name: str = "",
315
+ placeholder: str = "",
316
+ value: str = "",
317
+ min: str = "",
318
+ max: str = "",
319
+ step: str = "1",
320
+ required: bool = False,
321
+ **kwargs
322
+ ) -> str:
323
+ """Number input component"""
324
+ return Input(
325
+ name=name,
326
+ type="number",
327
+ placeholder=placeholder,
328
+ value=value,
329
+ required=required
330
+ )
331
+
332
+
333
+ __all__ = [
334
+ 'Input',
335
+ 'TextArea',
336
+ 'Select',
337
+ 'Checkbox',
338
+ 'Radio',
339
+ 'RadioGroup',
340
+ 'Form',
341
+ 'FormGroup',
342
+ 'FileInput',
343
+ 'PasswordInput',
344
+ 'EmailInput',
345
+ 'NumberInput',
346
+ ]