nextpy-framework 1.1.0__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.
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/__init__.py +52 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/auth.py +94 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/builder.py +123 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/cli.py +595 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/__init__.py +45 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/feedback.py +210 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/form.py +346 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/head.py +167 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/hooks_provider.py +64 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/image.py +180 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/layout.py +206 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/link.py +132 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/loader.py +65 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/toast.py +101 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/components/visual.py +185 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/config.py +75 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/core/__init__.py +21 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/core/builder.py +237 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/core/data_fetching.py +221 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/core/renderer.py +252 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/core/router.py +233 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/core/sync.py +34 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/db.py +121 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/dev_server.py +69 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/dev_tools.py +157 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/errors.py +70 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/hooks.py +348 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/performance.py +78 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/plugins.py +61 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/py.typed +0 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/server/__init__.py +6 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/server/app.py +325 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/server/debug.py +93 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/server/middleware.py +88 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/utils/__init__.py +0 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/utils/cache.py +89 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/utils/email.py +59 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/utils/file_upload.py +65 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/utils/logging.py +52 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/utils/search.py +59 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/utils/seo.py +85 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/utils/validators.py +58 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy/websocket.py +76 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy_framework.egg-info/PKG-INFO +343 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy_framework.egg-info/SOURCES.txt +53 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy_framework.egg-info/dependency_links.txt +1 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy_framework.egg-info/entry_points.txt +2 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy_framework.egg-info/requires.txt +16 -0
- nextpy_framework-1.1.0/.nextpy_framework/nextpy_framework.egg-info/top_level.txt +1 -0
- nextpy_framework-1.1.0/LICENSE +21 -0
- nextpy_framework-1.1.0/PKG-INFO +343 -0
- nextpy_framework-1.1.0/README.md +304 -0
- nextpy_framework-1.1.0/pyproject.toml +70 -0
- nextpy_framework-1.1.0/setup.cfg +4 -0
- nextpy_framework-1.1.0/tests/test_routing.py +50 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NextPy - A Python web framework inspired by Next.js
|
|
3
|
+
File-based routing, SSR, SSG, and more with FastAPI + Jinja2
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "1.0.1"
|
|
7
|
+
|
|
8
|
+
from nextpy.core.router import Router, Route, DynamicRoute
|
|
9
|
+
from nextpy.core.renderer import Renderer
|
|
10
|
+
from nextpy.core.data_fetching import (
|
|
11
|
+
get_server_side_props,
|
|
12
|
+
get_static_props,
|
|
13
|
+
get_static_paths,
|
|
14
|
+
)
|
|
15
|
+
from nextpy.components.head import Head
|
|
16
|
+
from nextpy.components.link import Link
|
|
17
|
+
from nextpy.server.app import create_app
|
|
18
|
+
from nextpy.hooks import (
|
|
19
|
+
useState,
|
|
20
|
+
useEffect,
|
|
21
|
+
useContext,
|
|
22
|
+
useReducer,
|
|
23
|
+
useCallback,
|
|
24
|
+
useMemo,
|
|
25
|
+
useRef,
|
|
26
|
+
useGlobalState,
|
|
27
|
+
component,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"Router",
|
|
32
|
+
"Route",
|
|
33
|
+
"DynamicRoute",
|
|
34
|
+
"Renderer",
|
|
35
|
+
"get_server_side_props",
|
|
36
|
+
"get_static_props",
|
|
37
|
+
"get_static_paths",
|
|
38
|
+
"Head",
|
|
39
|
+
"Link",
|
|
40
|
+
"create_app",
|
|
41
|
+
"useState",
|
|
42
|
+
"useEffect",
|
|
43
|
+
"useContext",
|
|
44
|
+
"useReducer",
|
|
45
|
+
"useCallback",
|
|
46
|
+
"useMemo",
|
|
47
|
+
"useRef",
|
|
48
|
+
"useGlobalState",
|
|
49
|
+
"component",
|
|
50
|
+
"maintainers",
|
|
51
|
+
"main"
|
|
52
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NextPy Authentication - JWT and Session support
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import jwt
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from typing import Optional, Dict, Any
|
|
8
|
+
from functools import wraps
|
|
9
|
+
from fastapi import HTTPException, Request
|
|
10
|
+
from nextpy.config import settings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AuthManager:
|
|
14
|
+
"""Handle JWT and session authentication"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def create_token(user_id: int, expires_delta: Optional[timedelta] = None) -> str:
|
|
18
|
+
"""Create JWT token"""
|
|
19
|
+
if expires_delta is None:
|
|
20
|
+
expires_delta = timedelta(hours=settings.jwt_expiration_hours)
|
|
21
|
+
|
|
22
|
+
expire = datetime.utcnow() + expires_delta
|
|
23
|
+
payload = {"user_id": user_id, "exp": expire}
|
|
24
|
+
|
|
25
|
+
return jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def verify_token(token: str) -> Optional[int]:
|
|
29
|
+
"""Verify JWT token and return user_id"""
|
|
30
|
+
try:
|
|
31
|
+
payload = jwt.decode(token, settings.jwt_secret, algorithms=[settings.jwt_algorithm])
|
|
32
|
+
user_id = payload.get("user_id")
|
|
33
|
+
if user_id is None:
|
|
34
|
+
return None
|
|
35
|
+
return user_id
|
|
36
|
+
except jwt.ExpiredSignatureError:
|
|
37
|
+
raise HTTPException(status_code=401, detail="Token expired")
|
|
38
|
+
except jwt.InvalidTokenError:
|
|
39
|
+
raise HTTPException(status_code=401, detail="Invalid token")
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def get_token_from_request(request: Request) -> Optional[str]:
|
|
43
|
+
"""Extract token from Authorization header"""
|
|
44
|
+
auth_header = request.headers.get("Authorization", "")
|
|
45
|
+
if auth_header.startswith("Bearer "):
|
|
46
|
+
return auth_header[7:]
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def require_auth(func):
|
|
51
|
+
"""Decorator to require authentication"""
|
|
52
|
+
@wraps(func)
|
|
53
|
+
async def wrapper(request: Request, *args, **kwargs):
|
|
54
|
+
token = AuthManager.get_token_from_request(request)
|
|
55
|
+
if not token:
|
|
56
|
+
raise HTTPException(status_code=401, detail="Missing token")
|
|
57
|
+
|
|
58
|
+
user_id = AuthManager.verify_token(token)
|
|
59
|
+
request.state.user_id = user_id
|
|
60
|
+
return await func(request, *args, **kwargs)
|
|
61
|
+
|
|
62
|
+
return wrapper
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Session storage (in-memory, use Redis for production)
|
|
66
|
+
_sessions: Dict[str, Dict[str, Any]] = {}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def create_session(user_id: int) -> str:
|
|
70
|
+
"""Create session"""
|
|
71
|
+
import uuid
|
|
72
|
+
session_id = str(uuid.uuid4())
|
|
73
|
+
_sessions[session_id] = {
|
|
74
|
+
"user_id": user_id,
|
|
75
|
+
"created_at": datetime.utcnow(),
|
|
76
|
+
"expires_at": datetime.utcnow() + timedelta(hours=24)
|
|
77
|
+
}
|
|
78
|
+
return session_id
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_session(session_id: str) -> Optional[Dict[str, Any]]:
|
|
82
|
+
"""Get session"""
|
|
83
|
+
session = _sessions.get(session_id)
|
|
84
|
+
if session and session["expires_at"] > datetime.utcnow():
|
|
85
|
+
return session
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def delete_session(session_id: str) -> bool:
|
|
90
|
+
"""Delete session"""
|
|
91
|
+
if session_id in _sessions:
|
|
92
|
+
del _sessions[session_id]
|
|
93
|
+
return True
|
|
94
|
+
return False
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NextPy Build Optimizer - Fast builds with caching and parallel processing
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Dict, Any, Optional
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BuildCache:
|
|
14
|
+
"""Smart build caching to skip unchanged files"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, cache_dir: str = ".nextpy_cache"):
|
|
17
|
+
self.cache_dir = Path(cache_dir)
|
|
18
|
+
self.cache_dir.mkdir(exist_ok=True)
|
|
19
|
+
self.cache_file = self.cache_dir / "build_cache.json"
|
|
20
|
+
self.cache: Dict[str, str] = self._load_cache()
|
|
21
|
+
|
|
22
|
+
def _load_cache(self) -> Dict[str, str]:
|
|
23
|
+
if self.cache_file.exists():
|
|
24
|
+
with open(self.cache_file) as f:
|
|
25
|
+
return json.load(f)
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
def _save_cache(self) -> None:
|
|
29
|
+
with open(self.cache_file, "w") as f:
|
|
30
|
+
json.dump(self.cache, f)
|
|
31
|
+
|
|
32
|
+
def get_hash(self, file_path: str) -> str:
|
|
33
|
+
"""Get SHA256 hash of file"""
|
|
34
|
+
with open(file_path, "rb") as f:
|
|
35
|
+
return hashlib.sha256(f.read()).hexdigest()
|
|
36
|
+
|
|
37
|
+
def is_changed(self, file_path: str) -> bool:
|
|
38
|
+
"""Check if file has changed since last build"""
|
|
39
|
+
current_hash = self.get_hash(file_path)
|
|
40
|
+
cached_hash = self.cache.get(file_path)
|
|
41
|
+
|
|
42
|
+
if cached_hash == current_hash:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
self.cache[file_path] = current_hash
|
|
46
|
+
self._save_cache()
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
def clear(self) -> None:
|
|
50
|
+
"""Clear cache"""
|
|
51
|
+
self.cache = {}
|
|
52
|
+
if self.cache_file.exists():
|
|
53
|
+
self.cache_file.unlink()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ParallelBuilder:
|
|
57
|
+
"""Build with parallel processing for faster builds"""
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
async def build_pages(pages: List[Path], concurrency: int = 4) -> List[Dict[str, Any]]:
|
|
61
|
+
"""Build multiple pages in parallel"""
|
|
62
|
+
results: List[Dict[str, Any]] = []
|
|
63
|
+
semaphore = asyncio.Semaphore(concurrency)
|
|
64
|
+
|
|
65
|
+
async def build_page(page: Path) -> Dict[str, Any]:
|
|
66
|
+
async with semaphore:
|
|
67
|
+
return await ParallelBuilder._build_single_page(page)
|
|
68
|
+
|
|
69
|
+
tasks = [build_page(page) for page in pages]
|
|
70
|
+
results = await asyncio.gather(*tasks)
|
|
71
|
+
return results
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
async def _build_single_page(page: Path) -> Dict[str, Any]:
|
|
75
|
+
"""Build a single page"""
|
|
76
|
+
await asyncio.sleep(0.1) # Simulate build work
|
|
77
|
+
return {
|
|
78
|
+
"page": str(page),
|
|
79
|
+
"status": "built",
|
|
80
|
+
"timestamp": datetime.now().isoformat()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class BuildOptimizer:
|
|
85
|
+
"""Optimize bundle size and build time"""
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def analyze_bundle(output_dir: Path) -> Dict[str, Any]:
|
|
89
|
+
"""Analyze built bundle size"""
|
|
90
|
+
total_size: int = 0
|
|
91
|
+
files: Dict[str, int] = {}
|
|
92
|
+
|
|
93
|
+
for file in output_dir.rglob("*"):
|
|
94
|
+
if file.is_file():
|
|
95
|
+
size = file.stat().st_size
|
|
96
|
+
total_size += size
|
|
97
|
+
files[str(file.relative_to(output_dir))] = size
|
|
98
|
+
|
|
99
|
+
# Sort by size
|
|
100
|
+
sorted_files = sorted(files.items(), key=lambda x: x[1], reverse=True)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"total_size": total_size,
|
|
104
|
+
"total_size_mb": round(total_size / 1024 / 1024, 2),
|
|
105
|
+
"files": dict(sorted_files[:10]), # Top 10 largest
|
|
106
|
+
"file_count": len(files)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def compress_assets(output_dir: Path) -> int:
|
|
111
|
+
"""Compress CSS and JS files"""
|
|
112
|
+
import gzip
|
|
113
|
+
compressed_count: int = 0
|
|
114
|
+
|
|
115
|
+
for file in list(output_dir.rglob("*.js")) + list(output_dir.rglob("*.css")):
|
|
116
|
+
if file.is_file():
|
|
117
|
+
with open(file, "rb") as f_in:
|
|
118
|
+
content = f_in.read()
|
|
119
|
+
with gzip.open(f"{file}.gz", "wb") as f_out:
|
|
120
|
+
f_out.write(content)
|
|
121
|
+
compressed_count += 1
|
|
122
|
+
|
|
123
|
+
return compressed_count
|