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.
- nextpy/__init__.py +50 -0
- nextpy/auth.py +94 -0
- nextpy/builder.py +123 -0
- nextpy/cli.py +490 -0
- nextpy/components/__init__.py +45 -0
- nextpy/components/feedback.py +210 -0
- nextpy/components/form.py +346 -0
- nextpy/components/head.py +167 -0
- nextpy/components/hooks_provider.py +64 -0
- nextpy/components/image.py +180 -0
- nextpy/components/layout.py +206 -0
- nextpy/components/link.py +132 -0
- nextpy/components/loader.py +65 -0
- nextpy/components/toast.py +101 -0
- nextpy/components/visual.py +185 -0
- nextpy/config.py +75 -0
- nextpy/core/__init__.py +21 -0
- nextpy/core/builder.py +237 -0
- nextpy/core/data_fetching.py +221 -0
- nextpy/core/renderer.py +252 -0
- nextpy/core/router.py +233 -0
- nextpy/core/sync.py +34 -0
- nextpy/db.py +121 -0
- nextpy/dev_server.py +69 -0
- nextpy/dev_tools.py +157 -0
- nextpy/errors.py +70 -0
- nextpy/hooks.py +348 -0
- nextpy/performance.py +78 -0
- nextpy/plugins.py +61 -0
- nextpy/py.typed +0 -0
- nextpy/server/__init__.py +6 -0
- nextpy/server/app.py +325 -0
- nextpy/server/debug.py +93 -0
- nextpy/server/middleware.py +88 -0
- nextpy/utils/__init__.py +0 -0
- nextpy/utils/cache.py +89 -0
- nextpy/utils/email.py +59 -0
- nextpy/utils/file_upload.py +65 -0
- nextpy/utils/logging.py +52 -0
- nextpy/utils/search.py +59 -0
- nextpy/utils/seo.py +85 -0
- nextpy/utils/validators.py +58 -0
- nextpy/websocket.py +76 -0
- nextpy_framework-1.0.0.dist-info/METADATA +343 -0
- nextpy_framework-1.0.0.dist-info/RECORD +49 -0
- nextpy_framework-1.0.0.dist-info/WHEEL +5 -0
- nextpy_framework-1.0.0.dist-info/entry_points.txt +2 -0
- nextpy_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- nextpy_framework-1.0.0.dist-info/top_level.txt +1 -0
nextpy/cli.py
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NextPy CLI - Command-line interface for NextPy projects
|
|
3
|
+
Commands: dev, build, start
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import asyncio
|
|
9
|
+
import signal
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
import uvicorn
|
|
15
|
+
from watchdog.observers import Observer
|
|
16
|
+
from watchdog.events import FileSystemEventHandler
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HotReloadHandler(FileSystemEventHandler):
|
|
20
|
+
"""Handles file system changes for hot reload"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, reload_callback):
|
|
23
|
+
self.reload_callback = reload_callback
|
|
24
|
+
self._debounce_timer = None
|
|
25
|
+
|
|
26
|
+
def on_modified(self, event):
|
|
27
|
+
if event.is_directory:
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
extensions = (".py", ".html", ".jinja2", ".css", ".js")
|
|
31
|
+
if isinstance(event.src_path, str) and any(event.src_path.endswith(ext) for ext in extensions):
|
|
32
|
+
self._trigger_reload()
|
|
33
|
+
|
|
34
|
+
def on_created(self, event):
|
|
35
|
+
if not event.is_directory:
|
|
36
|
+
self._trigger_reload()
|
|
37
|
+
|
|
38
|
+
def on_deleted(self, event):
|
|
39
|
+
if not event.is_directory:
|
|
40
|
+
self._trigger_reload()
|
|
41
|
+
|
|
42
|
+
def _trigger_reload(self):
|
|
43
|
+
if self.reload_callback:
|
|
44
|
+
self.reload_callback()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@click.group()
|
|
48
|
+
@click.version_option(version="1.0.0", prog_name="NextPy")
|
|
49
|
+
def cli():
|
|
50
|
+
"""NextPy - A Python web framework inspired by Next.js"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@cli.command()
|
|
55
|
+
@click.option("--port", "-p", default=5000, help="Port to run the server on")
|
|
56
|
+
@click.option("--host", "-h", default="0.0.0.0", help="Host to bind to")
|
|
57
|
+
@click.option("--reload/--no-reload", default=True, help="Enable hot reload")
|
|
58
|
+
@click.option("--debug/--no-debug", default=True, help="Enable debug mode")
|
|
59
|
+
def dev(port: int, host: str, reload: bool, debug: bool):
|
|
60
|
+
"""Start the development server with hot reload"""
|
|
61
|
+
click.echo(click.style("\n NextPy Development Server", fg="cyan", bold=True))
|
|
62
|
+
click.echo(click.style(" ========================\n", fg="cyan"))
|
|
63
|
+
|
|
64
|
+
_ensure_project_structure()
|
|
65
|
+
|
|
66
|
+
click.echo(f" - Mode: {'Development' if debug else 'Production'}")
|
|
67
|
+
click.echo(f" - Host: {host} (accessible at http://localhost:{port})")
|
|
68
|
+
click.echo(f" - Port: {port}")
|
|
69
|
+
click.echo(f" - Reload: {'Enabled' if reload else 'Disabled'}")
|
|
70
|
+
click.echo(f"\n ✨ Server ready at http://0.0.0.0:{port}")
|
|
71
|
+
click.echo(f" 🌐 Open http://localhost:{port} in your browser\n")
|
|
72
|
+
|
|
73
|
+
os.chdir(Path.cwd())
|
|
74
|
+
|
|
75
|
+
if reload:
|
|
76
|
+
uvicorn.run(
|
|
77
|
+
"main:app",
|
|
78
|
+
host=host,
|
|
79
|
+
port=port,
|
|
80
|
+
reload=True,
|
|
81
|
+
reload_dirs=["pages", "templates", "nextpy"],
|
|
82
|
+
log_level="info",
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
uvicorn.run(
|
|
86
|
+
"main:app",
|
|
87
|
+
host=host,
|
|
88
|
+
port=port,
|
|
89
|
+
log_level="info",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@cli.command()
|
|
94
|
+
@click.option("--out", "-o", default="out", help="Output directory for static files")
|
|
95
|
+
@click.option("--clean/--no-clean", default=True, help="Clean output directory first")
|
|
96
|
+
def build(out: str, clean: bool):
|
|
97
|
+
"""Build the project for production (SSG)"""
|
|
98
|
+
click.echo(click.style("\n NextPy Static Build", fg="green", bold=True))
|
|
99
|
+
click.echo(click.style(" ===================\n", fg="green"))
|
|
100
|
+
|
|
101
|
+
from nextpy.core.builder import Builder
|
|
102
|
+
|
|
103
|
+
builder = Builder(out_dir=out)
|
|
104
|
+
|
|
105
|
+
async def run_build():
|
|
106
|
+
manifest = await builder.build(clean=clean)
|
|
107
|
+
return manifest
|
|
108
|
+
|
|
109
|
+
manifest = asyncio.run(run_build())
|
|
110
|
+
|
|
111
|
+
pages_count = len(manifest.get("pages", {}))
|
|
112
|
+
click.echo(f"\n Built {pages_count} pages")
|
|
113
|
+
click.echo(f" Output: {out}/")
|
|
114
|
+
click.echo(click.style("\n Build complete!\n", fg="green", bold=True))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@cli.command()
|
|
118
|
+
@click.option("--port", "-p", default=5000, help="Port to run the server on")
|
|
119
|
+
@click.option("--host", "-h", default="0.0.0.0", help="Host to bind to")
|
|
120
|
+
def start(port: int, host: str):
|
|
121
|
+
"""Start the production server"""
|
|
122
|
+
click.echo(click.style("\n NextPy Production Server", fg="green", bold=True))
|
|
123
|
+
click.echo(click.style(" ========================\n", fg="green"))
|
|
124
|
+
|
|
125
|
+
click.echo(f" - Mode: Production")
|
|
126
|
+
click.echo(f" - Host: {host} (accessible at http://localhost:{port})")
|
|
127
|
+
click.echo(f" - Port: {port}")
|
|
128
|
+
click.echo(f"\n ✨ Server ready at http://0.0.0.0:{port}")
|
|
129
|
+
click.echo(f" 🌐 Open http://localhost:{port} in your browser\n")
|
|
130
|
+
|
|
131
|
+
os.chdir(Path.cwd())
|
|
132
|
+
|
|
133
|
+
uvicorn.run(
|
|
134
|
+
"main:app",
|
|
135
|
+
host=host,
|
|
136
|
+
port=port,
|
|
137
|
+
workers=4,
|
|
138
|
+
log_level="warning",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@cli.command()
|
|
143
|
+
@click.argument("name")
|
|
144
|
+
def create(name: str):
|
|
145
|
+
"""Create a new NextPy project"""
|
|
146
|
+
click.echo(click.style(f"\n Creating NextPy project: {name}", fg="cyan", bold=True))
|
|
147
|
+
click.echo(click.style(" " + "=" * (25 + len(name)) + "\n", fg="cyan"))
|
|
148
|
+
|
|
149
|
+
project_dir = Path(name)
|
|
150
|
+
|
|
151
|
+
if project_dir.exists():
|
|
152
|
+
click.echo(click.style(f" Error: Directory '{name}' already exists", fg="red"))
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
_create_project_structure(project_dir)
|
|
156
|
+
|
|
157
|
+
click.echo(f"\n Project created at: {project_dir.absolute()}")
|
|
158
|
+
click.echo(f"\n Next steps:")
|
|
159
|
+
click.echo(f" cd {name}")
|
|
160
|
+
click.echo(f" pip install -r requirements.txt")
|
|
161
|
+
click.echo(f" nextpy dev")
|
|
162
|
+
click.echo()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@cli.command()
|
|
166
|
+
def routes():
|
|
167
|
+
"""Display all registered routes"""
|
|
168
|
+
click.echo(click.style("\n NextPy Routes", fg="cyan", bold=True))
|
|
169
|
+
click.echo(click.style(" =============\n", fg="cyan"))
|
|
170
|
+
|
|
171
|
+
from nextpy.core.router import Router
|
|
172
|
+
|
|
173
|
+
router = Router()
|
|
174
|
+
router.scan_pages()
|
|
175
|
+
|
|
176
|
+
click.echo(" Page Routes:")
|
|
177
|
+
for route in router.routes:
|
|
178
|
+
dynamic = " (dynamic)" if route.is_dynamic else ""
|
|
179
|
+
click.echo(f" {route.path}{dynamic} -> {route.file_path}")
|
|
180
|
+
|
|
181
|
+
click.echo("\n API Routes:")
|
|
182
|
+
for route in router.api_routes:
|
|
183
|
+
dynamic = " (dynamic)" if route.is_dynamic else ""
|
|
184
|
+
click.echo(f" {route.path}{dynamic} -> {route.file_path}")
|
|
185
|
+
|
|
186
|
+
click.echo()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _ensure_project_structure():
|
|
190
|
+
"""Ensure the basic project structure exists"""
|
|
191
|
+
dirs = ["pages", "pages/api", "templates", "public", "public/css", "public/js"]
|
|
192
|
+
|
|
193
|
+
for dir_path in dirs:
|
|
194
|
+
Path(dir_path).mkdir(parents=True, exist_ok=True)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _create_project_structure(project_dir: Path):
|
|
198
|
+
"""Create a new project structure"""
|
|
199
|
+
dirs = [
|
|
200
|
+
"pages",
|
|
201
|
+
"pages/api",
|
|
202
|
+
"pages/blog",
|
|
203
|
+
"pages/api/users",
|
|
204
|
+
"templates",
|
|
205
|
+
"templates/components",
|
|
206
|
+
"public",
|
|
207
|
+
"public/css",
|
|
208
|
+
"public/js",
|
|
209
|
+
"public/images",
|
|
210
|
+
"models",
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
for dir_path in dirs:
|
|
214
|
+
(project_dir / dir_path).mkdir(parents=True, exist_ok=True)
|
|
215
|
+
click.echo(f" Created: {dir_path}/")
|
|
216
|
+
|
|
217
|
+
(project_dir / "pages" / "index.py").write_text('''"""Homepage"""
|
|
218
|
+
|
|
219
|
+
def get_template():
|
|
220
|
+
return "index.html"
|
|
221
|
+
|
|
222
|
+
async def get_server_side_props(context):
|
|
223
|
+
return {
|
|
224
|
+
"props": {
|
|
225
|
+
"title": "Welcome to NextPy",
|
|
226
|
+
"message": "Your Python-powered web framework"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
''')
|
|
230
|
+
click.echo(" Created: pages/index.py")
|
|
231
|
+
|
|
232
|
+
(project_dir / "templates" / "_base.html").write_text('''<!DOCTYPE html>
|
|
233
|
+
<html lang="en">
|
|
234
|
+
<head>
|
|
235
|
+
<meta charset="UTF-8">
|
|
236
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
237
|
+
<title>{% block title %}NextPy App{% endblock %}</title>
|
|
238
|
+
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
|
239
|
+
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
|
240
|
+
{% block head %}{% endblock %}
|
|
241
|
+
</head>
|
|
242
|
+
<body>
|
|
243
|
+
<div id="main-content">
|
|
244
|
+
{% block content %}{{ content | safe }}{% endblock %}
|
|
245
|
+
</div>
|
|
246
|
+
{% block scripts %}{% endblock %}
|
|
247
|
+
</body>
|
|
248
|
+
</html>
|
|
249
|
+
''')
|
|
250
|
+
click.echo(" Created: templates/_base.html")
|
|
251
|
+
|
|
252
|
+
(project_dir / "templates" / "index.html").write_text('''{% extends "_base.html" %}
|
|
253
|
+
|
|
254
|
+
{% block title %}{{ title }}{% endblock %}
|
|
255
|
+
|
|
256
|
+
{% block content %}
|
|
257
|
+
<div class="min-h-screen bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
|
|
258
|
+
<div class="text-center text-white">
|
|
259
|
+
<h1 class="text-5xl font-bold mb-4">{{ title }}</h1>
|
|
260
|
+
<p class="text-xl">{{ message }}</p>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
{% endblock %}
|
|
264
|
+
''')
|
|
265
|
+
click.echo(" Created: templates/index.html")
|
|
266
|
+
|
|
267
|
+
(project_dir / "main.py").write_text('''"""NextPy Application Entry Point"""
|
|
268
|
+
|
|
269
|
+
from nextpy import create_app
|
|
270
|
+
|
|
271
|
+
app = create_app(debug=True)
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
import uvicorn
|
|
275
|
+
uvicorn.run(app, host="0.0.0.0", port=5000)
|
|
276
|
+
''')
|
|
277
|
+
click.echo(" Created: main.py")
|
|
278
|
+
|
|
279
|
+
(project_dir / "requirements.txt").write_text('''fastapi>=0.100.0
|
|
280
|
+
uvicorn>=0.23.0
|
|
281
|
+
jinja2>=3.1.0
|
|
282
|
+
pydantic>=2.0.0
|
|
283
|
+
pydantic-settings>=2.0.0
|
|
284
|
+
click>=8.1.0
|
|
285
|
+
watchdog>=3.0.0
|
|
286
|
+
python-multipart>=0.0.6
|
|
287
|
+
pillow>=10.0.0
|
|
288
|
+
aiofiles>=23.0.0
|
|
289
|
+
httpx>=0.24.0
|
|
290
|
+
sqlalchemy>=2.0.0
|
|
291
|
+
python-dotenv>=1.0.0
|
|
292
|
+
pyjwt>=2.8.0
|
|
293
|
+
''')
|
|
294
|
+
click.echo(" Created: requirements.txt")
|
|
295
|
+
|
|
296
|
+
(project_dir / "pages" / "about.py").write_text('''"""About page"""
|
|
297
|
+
|
|
298
|
+
def get_template():
|
|
299
|
+
return "about.html"
|
|
300
|
+
|
|
301
|
+
async def get_server_side_props(context):
|
|
302
|
+
return {
|
|
303
|
+
"props": {
|
|
304
|
+
"title": "About NextPy",
|
|
305
|
+
"description": "Learn about the NextPy framework"
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
''')
|
|
309
|
+
click.echo(" Created: pages/about.py")
|
|
310
|
+
|
|
311
|
+
(project_dir / "pages" / "blog" / "index.py").write_text('''"""Blog listing"""
|
|
312
|
+
|
|
313
|
+
def get_template():
|
|
314
|
+
return "blog/index.html"
|
|
315
|
+
|
|
316
|
+
async def get_server_side_props(context):
|
|
317
|
+
posts = [
|
|
318
|
+
{"slug": "hello", "title": "Hello World"},
|
|
319
|
+
{"slug": "welcome", "title": "Welcome to NextPy"}
|
|
320
|
+
]
|
|
321
|
+
return {"props": {"posts": posts}}
|
|
322
|
+
''')
|
|
323
|
+
click.echo(" Created: pages/blog/index.py")
|
|
324
|
+
|
|
325
|
+
(project_dir / "pages" / "blog" / "[slug].py").write_text('''"""Dynamic blog post"""
|
|
326
|
+
|
|
327
|
+
def get_template():
|
|
328
|
+
return "blog/post.html"
|
|
329
|
+
|
|
330
|
+
async def get_server_side_props(context):
|
|
331
|
+
slug = context["params"]["slug"]
|
|
332
|
+
return {
|
|
333
|
+
"props": {
|
|
334
|
+
"slug": slug,
|
|
335
|
+
"title": f"Post: {slug}",
|
|
336
|
+
"content": "Blog post content here"
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
''')
|
|
340
|
+
click.echo(" Created: pages/blog/[slug].py")
|
|
341
|
+
|
|
342
|
+
(project_dir / "pages" / "api" / "posts.py").write_text('''"""API route for posts"""
|
|
343
|
+
|
|
344
|
+
async def get(request):
|
|
345
|
+
"""GET /api/posts"""
|
|
346
|
+
return {"posts": [{"id": 1, "title": "Post 1"}]}
|
|
347
|
+
|
|
348
|
+
async def post(request):
|
|
349
|
+
"""POST /api/posts"""
|
|
350
|
+
body = await request.json()
|
|
351
|
+
return {"id": 2, "title": body.get("title")}, 201
|
|
352
|
+
''')
|
|
353
|
+
click.echo(" Created: pages/api/posts.py")
|
|
354
|
+
|
|
355
|
+
(project_dir / "pages" / "api" / "users" / "[id].py").write_text('''"""Dynamic API route"""
|
|
356
|
+
|
|
357
|
+
async def get(request):
|
|
358
|
+
"""GET /api/users/:id"""
|
|
359
|
+
user_id = request.path_params["id"]
|
|
360
|
+
return {"id": user_id, "name": f"User {user_id}"}
|
|
361
|
+
|
|
362
|
+
async def put(request):
|
|
363
|
+
"""PUT /api/users/:id"""
|
|
364
|
+
user_id = request.path_params["id"]
|
|
365
|
+
body = await request.json()
|
|
366
|
+
return {"id": user_id, "updated": True}
|
|
367
|
+
|
|
368
|
+
async def delete(request):
|
|
369
|
+
"""DELETE /api/users/:id"""
|
|
370
|
+
user_id = request.path_params["id"]
|
|
371
|
+
return {"deleted": True}, 204
|
|
372
|
+
''')
|
|
373
|
+
click.echo(" Created: pages/api/users/[id].py")
|
|
374
|
+
|
|
375
|
+
(project_dir / "templates" / "components" / "button.html").write_text('''{% macro button(text, href="", onclick="", variant="primary", disabled=false) %}
|
|
376
|
+
<a href="{{ href }}" class="px-4 py-2 rounded-lg font-semibold transition" style="background: {% if variant == 'primary' %}#3b82f6{% else %}#6b7280{% endif %}; color: white;">
|
|
377
|
+
{{ text }}
|
|
378
|
+
</a>
|
|
379
|
+
{% endmacro %}
|
|
380
|
+
''')
|
|
381
|
+
click.echo(" Created: templates/components/button.html")
|
|
382
|
+
|
|
383
|
+
(project_dir / "templates" / "components" / "card.html").write_text('''{% macro card(title="", content="", footer="") %}
|
|
384
|
+
<div class="bg-white rounded-lg shadow-md p-6 mb-4">
|
|
385
|
+
{% if title %}<h3 class="font-bold text-lg mb-2">{{ title }}</h3>{% endif %}
|
|
386
|
+
{% if content %}<p class="text-gray-600">{{ content }}</p>{% endif %}
|
|
387
|
+
{% if footer %}<div class="mt-4 text-sm text-gray-500">{{ footer }}</div>{% endif %}
|
|
388
|
+
</div>
|
|
389
|
+
{% endmacro %}
|
|
390
|
+
''')
|
|
391
|
+
click.echo(" Created: templates/components/card.html")
|
|
392
|
+
|
|
393
|
+
(project_dir / "templates" / "components" / "modal.html").write_text('''{% macro modal(id="", title="", content="") %}
|
|
394
|
+
<div id="{{ id }}" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1000;">
|
|
395
|
+
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 24px; border-radius: 8px; min-width: 400px;">
|
|
396
|
+
{% if title %}<h2 class="text-2xl font-bold mb-4">{{ title }}</h2>{% endif %}
|
|
397
|
+
{% if content %}<p class="text-gray-600 mb-6">{{ content }}</p>{% endif %}
|
|
398
|
+
<button onclick="document.getElementById('{{ id }}').style.display = 'none';" class="px-4 py-2 bg-blue-600 text-white rounded">Close</button>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
{% endmacro %}
|
|
402
|
+
''')
|
|
403
|
+
click.echo(" Created: templates/components/modal.html")
|
|
404
|
+
|
|
405
|
+
(project_dir / "models" / "user.py").write_text('''"""User database model"""
|
|
406
|
+
|
|
407
|
+
from nextpy.db import Base
|
|
408
|
+
from sqlalchemy import Column, Integer, String, DateTime
|
|
409
|
+
from datetime import datetime
|
|
410
|
+
|
|
411
|
+
class User(Base):
|
|
412
|
+
__tablename__ = "users"
|
|
413
|
+
|
|
414
|
+
id = Column(Integer, primary_key=True)
|
|
415
|
+
name = Column(String(255))
|
|
416
|
+
email = Column(String(255), unique=True)
|
|
417
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
418
|
+
|
|
419
|
+
def __repr__(self):
|
|
420
|
+
return f"<User {self.name}>"
|
|
421
|
+
''')
|
|
422
|
+
click.echo(" Created: models/user.py")
|
|
423
|
+
|
|
424
|
+
(project_dir / "nextpy.config.py").write_text('''"""NextPy Configuration"""
|
|
425
|
+
|
|
426
|
+
from pydantic_settings import BaseSettings
|
|
427
|
+
|
|
428
|
+
class Settings(BaseSettings):
|
|
429
|
+
app_name: str = "MyNextPyApp"
|
|
430
|
+
debug: bool = True
|
|
431
|
+
database_url: str = "sqlite:///app.db"
|
|
432
|
+
secret_key: str = "your-secret-key-change-in-production"
|
|
433
|
+
|
|
434
|
+
class Config:
|
|
435
|
+
env_file = ".env"
|
|
436
|
+
|
|
437
|
+
settings = Settings()
|
|
438
|
+
''')
|
|
439
|
+
click.echo(" Created: nextpy.config.py")
|
|
440
|
+
|
|
441
|
+
(project_dir / ".env").write_text('''DATABASE_URL=sqlite:///app.db
|
|
442
|
+
DEBUG=True
|
|
443
|
+
SECRET_KEY=your-secret-key-change-in-production
|
|
444
|
+
SMTP_HOST=smtp.gmail.com
|
|
445
|
+
SMTP_PORT=587
|
|
446
|
+
SMTP_USER=your-email@gmail.com
|
|
447
|
+
SMTP_PASSWORD=your-password
|
|
448
|
+
''')
|
|
449
|
+
click.echo(" Created: .env")
|
|
450
|
+
|
|
451
|
+
(project_dir / ".gitignore").write_text('''# Python
|
|
452
|
+
__pycache__/
|
|
453
|
+
*.py[cod]
|
|
454
|
+
*$py.class
|
|
455
|
+
*.so
|
|
456
|
+
.Python
|
|
457
|
+
env/
|
|
458
|
+
venv/
|
|
459
|
+
*.egg-info/
|
|
460
|
+
dist/
|
|
461
|
+
build/
|
|
462
|
+
|
|
463
|
+
# Database
|
|
464
|
+
*.db
|
|
465
|
+
*.sqlite3
|
|
466
|
+
|
|
467
|
+
# Environment
|
|
468
|
+
.env
|
|
469
|
+
.env.local
|
|
470
|
+
|
|
471
|
+
# IDE
|
|
472
|
+
.vscode/
|
|
473
|
+
.idea/
|
|
474
|
+
*.swp
|
|
475
|
+
*.swo
|
|
476
|
+
*~
|
|
477
|
+
|
|
478
|
+
# OS
|
|
479
|
+
.DS_Store
|
|
480
|
+
Thumbs.db
|
|
481
|
+
|
|
482
|
+
# Build output
|
|
483
|
+
out/
|
|
484
|
+
.nextpy/
|
|
485
|
+
''')
|
|
486
|
+
click.echo(" Created: .gitignore")
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
if __name__ == "__main__":
|
|
490
|
+
cli()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""NextPy Components - Reusable UI components"""
|
|
2
|
+
|
|
3
|
+
from nextpy.components.head import Head
|
|
4
|
+
from nextpy.components.link import Link
|
|
5
|
+
from nextpy.components.image import Image
|
|
6
|
+
from nextpy.components import form, feedback, layout, loader, toast, visual, hooks_provider
|
|
7
|
+
|
|
8
|
+
# Form components
|
|
9
|
+
from nextpy.components.form import (
|
|
10
|
+
Input, TextArea, Select, Checkbox, Radio, RadioGroup, Form, FormGroup, FileInput,
|
|
11
|
+
NumberInput, DateInput, TimeInput, PasswordInput, RangeInput, ColorInput
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Feedback components
|
|
15
|
+
from nextpy.components.feedback import Alert, Badge, Progress
|
|
16
|
+
|
|
17
|
+
# Layout components
|
|
18
|
+
from nextpy.components.layout import Grid, Flex, Stack, Container, Sidebar
|
|
19
|
+
|
|
20
|
+
# Loader components
|
|
21
|
+
from nextpy.components.loader import spinner, skeleton, progress_bar, loading_screen
|
|
22
|
+
|
|
23
|
+
# Toast components
|
|
24
|
+
from nextpy.components.toast import Toast, get_toast, toast_html
|
|
25
|
+
|
|
26
|
+
# Visual components
|
|
27
|
+
from nextpy.components.visual import (
|
|
28
|
+
Tabs, Accordion, Dropdown, Modal, Card, Breadcrumb, Pagination
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Hooks provider
|
|
32
|
+
from nextpy.components.hooks_provider import HooksProvider, HooksContext, with_hooks
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"Head", "Link", "Image",
|
|
36
|
+
"Input", "TextArea", "Select", "Checkbox", "Radio", "RadioGroup", "Form", "FormGroup", "FileInput",
|
|
37
|
+
"NumberInput", "DateInput", "TimeInput", "PasswordInput", "RangeInput", "ColorInput",
|
|
38
|
+
"Alert", "Badge", "Progress",
|
|
39
|
+
"Grid", "Flex", "Stack", "Container", "Sidebar",
|
|
40
|
+
"spinner", "skeleton", "progress_bar", "loading_screen",
|
|
41
|
+
"Toast", "get_toast", "toast_html",
|
|
42
|
+
"Tabs", "Accordion", "Dropdown", "Modal", "Card", "Breadcrumb", "Pagination",
|
|
43
|
+
"HooksProvider", "HooksContext", "with_hooks",
|
|
44
|
+
"form", "feedback", "layout", "loader", "toast", "visual", "hooks_provider"
|
|
45
|
+
]
|