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,65 @@
1
+ """
2
+ NextPy File Upload Utilities
3
+ Handle file uploads with validation and storage
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Optional
9
+ from fastapi import UploadFile
10
+ import shutil
11
+
12
+
13
+ UPLOAD_DIR = Path("uploads")
14
+ ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".pdf", ".txt", ".doc", ".docx"}
15
+ MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
16
+
17
+
18
+ async def upload_file(
19
+ file: UploadFile,
20
+ directory: str = "general",
21
+ max_size: int = MAX_FILE_SIZE,
22
+ ) -> Optional[str]:
23
+ """Upload file with validation"""
24
+
25
+ # Validate extension
26
+ ext = Path(file.filename).suffix.lower()
27
+ if ext not in ALLOWED_EXTENSIONS:
28
+ raise ValueError(f"File type {ext} not allowed")
29
+
30
+ # Create upload directory
31
+ upload_path = UPLOAD_DIR / directory
32
+ upload_path.mkdir(parents=True, exist_ok=True)
33
+
34
+ # Save file
35
+ file_path = upload_path / file.filename
36
+
37
+ try:
38
+ with open(file_path, "wb") as f:
39
+ content = await file.read()
40
+ if len(content) > max_size:
41
+ raise ValueError(f"File too large (max {max_size} bytes)")
42
+ f.write(content)
43
+
44
+ return str(file_path)
45
+ except Exception as e:
46
+ if file_path.exists():
47
+ file_path.unlink()
48
+ raise e
49
+
50
+
51
+ async def delete_file(file_path: str) -> bool:
52
+ """Delete uploaded file"""
53
+ try:
54
+ path = Path(file_path)
55
+ if path.exists():
56
+ path.unlink()
57
+ return True
58
+ return False
59
+ except Exception:
60
+ return False
61
+
62
+
63
+ def get_upload_url(file_path: str) -> str:
64
+ """Get public URL for uploaded file"""
65
+ return f"/uploads/{file_path}"
@@ -0,0 +1,52 @@
1
+ """
2
+ NextPy Logging Utilities
3
+ Structured logging for debugging and monitoring
4
+ """
5
+
6
+ import logging
7
+ from datetime import datetime
8
+ from typing import Any
9
+
10
+
11
+ class Logger:
12
+ """Simple logging wrapper"""
13
+
14
+ def __init__(self, name: str):
15
+ self.logger = logging.getLogger(name)
16
+
17
+ if not self.logger.handlers:
18
+ handler = logging.StreamHandler()
19
+ formatter = logging.Formatter(
20
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
21
+ )
22
+ handler.setFormatter(formatter)
23
+ self.logger.addHandler(handler)
24
+ self.logger.setLevel(logging.INFO)
25
+
26
+ def info(self, message: str, **kwargs):
27
+ """Log info message"""
28
+ self.logger.info(f"{message} {self._format_kwargs(kwargs)}")
29
+
30
+ def error(self, message: str, **kwargs):
31
+ """Log error message"""
32
+ self.logger.error(f"{message} {self._format_kwargs(kwargs)}")
33
+
34
+ def debug(self, message: str, **kwargs):
35
+ """Log debug message"""
36
+ self.logger.debug(f"{message} {self._format_kwargs(kwargs)}")
37
+
38
+ def warning(self, message: str, **kwargs):
39
+ """Log warning message"""
40
+ self.logger.warning(f"{message} {self._format_kwargs(kwargs)}")
41
+
42
+ @staticmethod
43
+ def _format_kwargs(kwargs: dict) -> str:
44
+ """Format kwargs for logging"""
45
+ if not kwargs:
46
+ return ""
47
+ return " | " + " ".join(f"{k}={v}" for k, v in kwargs.items())
48
+
49
+
50
+ def get_logger(name: str) -> Logger:
51
+ """Get logger instance"""
52
+ return Logger(name)
nextpy/utils/search.py ADDED
@@ -0,0 +1,59 @@
1
+ """
2
+ NextPy Search Utilities
3
+ Simple full-text search functionality
4
+ """
5
+
6
+ from typing import List, Dict, Any
7
+ import re
8
+
9
+
10
+ def simple_search(query: str, items: List[Dict[str, Any]], fields: List[str]) -> List[Dict[str, Any]]:
11
+ """Simple full-text search across multiple fields"""
12
+ query_lower = query.lower()
13
+ results = []
14
+
15
+ for item in items:
16
+ for field in fields:
17
+ if field in item:
18
+ value = str(item.get(field, "")).lower()
19
+ if query_lower in value:
20
+ results.append(item)
21
+ break
22
+
23
+ return results
24
+
25
+
26
+ def fuzzy_search(query: str, items: List[Dict[str, Any]], field: str) -> List[Dict[str, Any]]:
27
+ """Fuzzy search with scoring"""
28
+ def fuzzy_match(pattern: str, text: str) -> int:
29
+ """Calculate fuzzy match score"""
30
+ pattern = pattern.lower()
31
+ text = text.lower()
32
+ score = 0
33
+ j = 0
34
+
35
+ for i, char in enumerate(pattern):
36
+ pos = text.find(char, j)
37
+ if pos == -1:
38
+ return 0
39
+ score += 1 / (pos - j + 1)
40
+ j = pos + 1
41
+
42
+ return score
43
+
44
+ results = []
45
+ for item in items:
46
+ text = str(item.get(field, ""))
47
+ score = fuzzy_match(query, text)
48
+ if score > 0:
49
+ results.append((item, score))
50
+
51
+ # Sort by score descending
52
+ results.sort(key=lambda x: x[1], reverse=True)
53
+ return [item for item, score in results]
54
+
55
+
56
+ def search_highlight(query: str, text: str) -> str:
57
+ """Highlight search terms in text"""
58
+ pattern = re.compile(re.escape(query), re.IGNORECASE)
59
+ return pattern.sub(f'<mark>{query}</mark>', text)
nextpy/utils/seo.py ADDED
@@ -0,0 +1,85 @@
1
+ """
2
+ NextPy SEO Utilities
3
+ Helpers for SEO optimization, structured data, sitemaps
4
+ """
5
+
6
+ from typing import Dict, List, Any, Optional
7
+ import json
8
+ from datetime import datetime
9
+
10
+
11
+ def create_article_schema(
12
+ title: str,
13
+ description: str,
14
+ image: str,
15
+ author: str,
16
+ date_published: datetime,
17
+ date_modified: Optional[datetime] = None,
18
+ url: str = ""
19
+ ) -> Dict[str, Any]:
20
+ """Create structured data for article"""
21
+ return {
22
+ "@context": "https://schema.org",
23
+ "@type": "Article",
24
+ "headline": title,
25
+ "description": description,
26
+ "image": image,
27
+ "author": {
28
+ "@type": "Person",
29
+ "name": author
30
+ },
31
+ "datePublished": date_published.isoformat(),
32
+ "dateModified": (date_modified or date_published).isoformat(),
33
+ "url": url
34
+ }
35
+
36
+
37
+ def create_organization_schema(
38
+ name: str,
39
+ url: str,
40
+ logo: str,
41
+ contact_url: Optional[str] = None
42
+ ) -> Dict[str, Any]:
43
+ """Create structured data for organization"""
44
+ return {
45
+ "@context": "https://schema.org",
46
+ "@type": "Organization",
47
+ "name": name,
48
+ "url": url,
49
+ "logo": logo,
50
+ "contactPoint": {
51
+ "@type": "ContactPoint",
52
+ "contactType": "Customer Service",
53
+ "url": contact_url
54
+ } if contact_url else None
55
+ }
56
+
57
+
58
+ def generate_sitemap(routes: List[Dict[str, Any]]) -> str:
59
+ """Generate XML sitemap from routes"""
60
+ sitemap = '<?xml version="1.0" encoding="UTF-8"?>\n'
61
+ sitemap += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
62
+
63
+ for route in routes:
64
+ sitemap += " <url>\n"
65
+ sitemap += f" <loc>{route.get('url', '')}</loc>\n"
66
+ if route.get('lastmod'):
67
+ sitemap += f" <lastmod>{route['lastmod']}</lastmod>\n"
68
+ if route.get('priority'):
69
+ sitemap += f" <priority>{route['priority']}</priority>\n"
70
+ sitemap += " </url>\n"
71
+
72
+ sitemap += "</urlset>"
73
+ return sitemap
74
+
75
+
76
+ def generate_robots_txt(sitemap_url: str) -> str:
77
+ """Generate robots.txt content"""
78
+ return f"""User-agent: *
79
+ Allow: /
80
+ Disallow: /admin/
81
+ Disallow: /api/
82
+ Disallow: /_nextpy/
83
+
84
+ Sitemap: {sitemap_url}
85
+ """
@@ -0,0 +1,58 @@
1
+ """
2
+ NextPy Validation Utilities
3
+ Type-safe validation helpers using Pydantic
4
+ """
5
+
6
+ from pydantic import BaseModel, EmailStr, HttpUrl
7
+ from typing import Optional, List, Dict, Any
8
+
9
+
10
+ class ContactForm(BaseModel):
11
+ """Contact form validation"""
12
+ name: str
13
+ email: EmailStr
14
+ message: str
15
+ subject: Optional[str] = None
16
+
17
+
18
+ class BlogPost(BaseModel):
19
+ """Blog post validation"""
20
+ title: str
21
+ slug: str
22
+ content: str
23
+ excerpt: Optional[str] = None
24
+ featured_image: Optional[HttpUrl] = None
25
+ author: str
26
+ tags: List[str] = []
27
+ published: bool = False
28
+
29
+
30
+ class User(BaseModel):
31
+ """User profile validation"""
32
+ email: EmailStr
33
+ username: str
34
+ full_name: str
35
+ bio: Optional[str] = None
36
+ avatar_url: Optional[HttpUrl] = None
37
+
38
+
39
+ class LoginForm(BaseModel):
40
+ """Login form validation"""
41
+ email: EmailStr
42
+ password: str
43
+ remember_me: bool = False
44
+
45
+
46
+ class SignupForm(BaseModel):
47
+ """Signup form validation"""
48
+ email: EmailStr
49
+ username: str
50
+ password: str
51
+ confirm_password: str
52
+ agree_to_terms: bool = False
53
+
54
+
55
+ def validate_slug(slug: str) -> bool:
56
+ """Validate URL-safe slug format"""
57
+ import re
58
+ return bool(re.match(r"^[a-z0-9]+(?:-[a-z0-9]+)*$", slug))
nextpy/websocket.py ADDED
@@ -0,0 +1,76 @@
1
+ """
2
+ NextPy WebSocket Support
3
+ Real-time communication with clients
4
+ """
5
+
6
+ from fastapi import WebSocket
7
+ from typing import Set, Dict, Callable, Any
8
+ import json
9
+
10
+
11
+ class ConnectionManager:
12
+ """Manage WebSocket connections"""
13
+
14
+ def __init__(self):
15
+ self.active_connections: Set[WebSocket] = set()
16
+ self.subscribers: Dict[str, Set[WebSocket]] = {}
17
+
18
+ async def connect(self, websocket: WebSocket):
19
+ """Accept new connection"""
20
+ await websocket.accept()
21
+ self.active_connections.add(websocket)
22
+
23
+ async def disconnect(self, websocket: WebSocket):
24
+ """Remove connection"""
25
+ self.active_connections.discard(websocket)
26
+ for subs in self.subscribers.values():
27
+ subs.discard(websocket)
28
+
29
+ async def broadcast(self, message: Dict[str, Any]):
30
+ """Send message to all connections"""
31
+ for connection in self.active_connections:
32
+ try:
33
+ await connection.send_json(message)
34
+ except:
35
+ pass
36
+
37
+ async def subscribe(self, websocket: WebSocket, channel: str):
38
+ """Subscribe connection to channel"""
39
+ if channel not in self.subscribers:
40
+ self.subscribers[channel] = set()
41
+ self.subscribers[channel].add(websocket)
42
+
43
+ async def publish(self, channel: str, message: Dict[str, Any]):
44
+ """Publish message to channel subscribers"""
45
+ if channel in self.subscribers:
46
+ for connection in self.subscribers[channel]:
47
+ try:
48
+ await connection.send_json(message)
49
+ except:
50
+ pass
51
+
52
+
53
+ # Global manager instance
54
+ manager = ConnectionManager()
55
+
56
+
57
+ async def handle_websocket(websocket: WebSocket):
58
+ """Handle WebSocket connection"""
59
+ await manager.connect(websocket)
60
+ try:
61
+ while True:
62
+ data = await websocket.receive_text()
63
+ message = json.loads(data)
64
+
65
+ # Route message to appropriate handler
66
+ msg_type = message.get("type")
67
+ if msg_type == "subscribe":
68
+ await manager.subscribe(websocket, message.get("channel"))
69
+ elif msg_type == "publish":
70
+ await manager.publish(message.get("channel"), message.get("payload"))
71
+ elif msg_type == "broadcast":
72
+ await manager.broadcast(message.get("payload"))
73
+ except:
74
+ pass
75
+ finally:
76
+ await manager.disconnect(websocket)