nexios 0.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.
- nexios-0.1.0/PKG-INFO +249 -0
- nexios-0.1.0/nexios/__init__.py +33 -0
- nexios-0.1.0/nexios/application.py +161 -0
- nexios-0.1.0/nexios/background.py +44 -0
- nexios-0.1.0/nexios/cli/__init__.py +0 -0
- nexios-0.1.0/nexios/cli/create_project.py +161 -0
- nexios-0.1.0/nexios/cli/main.py +20 -0
- nexios-0.1.0/nexios/config/__init__.py +1 -0
- nexios-0.1.0/nexios/config/settings.py +21 -0
- nexios-0.1.0/nexios/decorators.py +62 -0
- nexios-0.1.0/nexios/filestorage.py +65 -0
- nexios-0.1.0/nexios/http/__init__.py +0 -0
- nexios-0.1.0/nexios/http/cookies_parser.py +30 -0
- nexios-0.1.0/nexios/http/mixins.py +32 -0
- nexios-0.1.0/nexios/http/parsers.py +161 -0
- nexios-0.1.0/nexios/http/request.py +208 -0
- nexios-0.1.0/nexios/http/response.py +550 -0
- nexios-0.1.0/nexios/middlewares/__init__.py +0 -0
- nexios-0.1.0/nexios/middlewares/base.py +16 -0
- nexios-0.1.0/nexios/middlewares/common.py +22 -0
- nexios-0.1.0/nexios/middlewares/cors.py +135 -0
- nexios-0.1.0/nexios/middlewares/logging.py +62 -0
- nexios-0.1.0/nexios/nexios.egg-info/PKG-INFO +238 -0
- nexios-0.1.0/nexios/nexios.egg-info/SOURCES.txt +0 -0
- nexios-0.1.0/nexios/nexios.egg-info/dependency_links.txt +1 -0
- nexios-0.1.0/nexios/nexios.egg-info/entry_points.txt +2 -0
- nexios-0.1.0/nexios/nexios.egg-info/requires.txt +6 -0
- nexios-0.1.0/nexios/nexios.egg-info/top_level.txt +1 -0
- nexios-0.1.0/nexios/routers.py +155 -0
- nexios-0.1.0/nexios/sessions/__init__.py +9 -0
- nexios-0.1.0/nexios/sessions/backends/__init__.py +0 -0
- nexios-0.1.0/nexios/sessions/backends/base.py +346 -0
- nexios-0.1.0/nexios/sessions/backends/db.py +117 -0
- nexios-0.1.0/nexios/sessions/backends/exceptions.py +40 -0
- nexios-0.1.0/nexios/sessions/middlewares.py +28 -0
- nexios-0.1.0/nexios/sessions/models.py +22 -0
- nexios-0.1.0/nexios/sessions/session_base.py +52 -0
- nexios-0.1.0/nexios/sessions/utils.py +26 -0
- nexios-0.1.0/nexios/static.py +54 -0
- nexios-0.1.0/nexios/structs.py +649 -0
- nexios-0.1.0/nexios/types.py +10 -0
- nexios-0.1.0/nexios/urlParsers.py +204 -0
- nexios-0.1.0/nexios/utils/__init__.py +0 -0
- nexios-0.1.0/nexios/utils/async_utils.py +18 -0
- nexios-0.1.0/nexios/utils/crypto.py +9 -0
- nexios-0.1.0/nexios/utils/cuncurrency.py +65 -0
- nexios-0.1.0/nexios/utils/db/__init__.py +0 -0
- nexios-0.1.0/nexios/utils/db/fields.py +79 -0
- nexios-0.1.0/nexios/utils/files.py +193 -0
- nexios-0.1.0/nexios/utils/signing.py +84 -0
- nexios-0.1.0/nexios/utils/timezone.py +336 -0
- nexios-0.1.0/nexios/validator/__init__.py +3 -0
- nexios-0.1.0/nexios/validator/base.py +84 -0
- nexios-0.1.0/nexios/validator/descriptor.py +49 -0
- nexios-0.1.0/nexios/validator/exceptions.py +4 -0
- nexios-0.1.0/nexios/validator/fields.py +419 -0
- nexios-0.1.0/nexios/validator/l.py +299 -0
- nexios-0.1.0/nexios/websockets/__init__.py +0 -0
- nexios-0.1.0/nexios/websockets/base.py +180 -0
- nexios-0.1.0/pyproject.toml +21 -0
- nexios-0.1.0/readme.md +224 -0
nexios-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nexios
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Nexio is a modern, high-performance ASGI web framework for Python.
|
|
5
|
+
Home-page: https://github.com/techwithdunamix/nexio
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Chidebele Dunamis
|
|
8
|
+
Author-email: techwithdunamix@example.com
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Dist: aerich
|
|
19
|
+
Requires-Dist: anyio
|
|
20
|
+
Requires-Dist: asgiref
|
|
21
|
+
Requires-Dist: tortoise-orm
|
|
22
|
+
Requires-Dist: uvicorn
|
|
23
|
+
Project-URL: Repository, https://github.com/techwithdunamix/nexio
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
# Nexio HTTP Framework 🚀💻
|
|
29
|
+
|
|
30
|
+
Welcome to Nexio — a lightning-fast web framework built with Python and written by TechWithDunamix, a Nigerian developer! 🇳🇬💡 Whether you're building APIs, web apps, or anything in between, Nexio is here to make your life easier and faster! ⚡
|
|
31
|
+
|
|
32
|
+
## Why Nexio? 🤔
|
|
33
|
+
|
|
34
|
+
- **Blazing Fast**: Built with performance in mind. Don't blink or you might miss it! ⚡
|
|
35
|
+
- **Simplicity**: Write less code, get more done. You'll spend less time debugging and more time building cool stuff. 👨💻
|
|
36
|
+
- **Easy to Use**: Everything is straightforward, from routing to database setup — no complicated setups, just plug and play! 🔌🎮
|
|
37
|
+
|
|
38
|
+
## Quick Start 🚀
|
|
39
|
+
|
|
40
|
+
Ready to get started? Here's how you can set up Nexio and make your first web app in minutes:
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### 1. Install Dependencies 📦
|
|
44
|
+
|
|
45
|
+
Install Nexio and the required dependencies with pip:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install git+https://github.com/TechWithDunamix/Nexio.git
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Create Your First App 💻
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
nexio create <app_name>
|
|
55
|
+
```
|
|
56
|
+
##### Outputs
|
|
57
|
+
```md
|
|
58
|
+
To get started
|
|
59
|
+
1. cd <app_name>
|
|
60
|
+
|
|
61
|
+
2. cd <app_name>
|
|
62
|
+
|
|
63
|
+
3. Your app will be available at http://localhost:8000
|
|
64
|
+
```
|
|
65
|
+
# Folder structure
|
|
66
|
+
|
|
67
|
+
```txt
|
|
68
|
+
project_name/
|
|
69
|
+
├── config/
|
|
70
|
+
│ ├── __init__.py
|
|
71
|
+
│ ├── database.py
|
|
72
|
+
│ └── settings.py
|
|
73
|
+
├── handlers/
|
|
74
|
+
│ ├── __init__.py
|
|
75
|
+
│ └── routes.py
|
|
76
|
+
└── main.py
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
### Basic Example
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from nexio import get_application
|
|
84
|
+
import uvicorn
|
|
85
|
+
from nexio.routers import Routes
|
|
86
|
+
app = get_application()
|
|
87
|
+
|
|
88
|
+
async def home(req,res):
|
|
89
|
+
res.json({"text" :"hello welcome to nexio"})
|
|
90
|
+
|
|
91
|
+
app.add_route(Routes("/",home))
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Nexio Project Structure Documentation
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
project_name/
|
|
102
|
+
├── config/
|
|
103
|
+
│ ├── __init__.py
|
|
104
|
+
│ ├── database.py
|
|
105
|
+
│ └── settings.py
|
|
106
|
+
├── handlers/
|
|
107
|
+
│ ├── __init__.py
|
|
108
|
+
│ └── routes.py
|
|
109
|
+
└── main.py
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## File Descriptions
|
|
113
|
+
|
|
114
|
+
### main.py
|
|
115
|
+
The application entry point and core configuration file.
|
|
116
|
+
```python
|
|
117
|
+
# Key responsibilities:
|
|
118
|
+
- Initializes the Nexio application
|
|
119
|
+
- Sets up database connections
|
|
120
|
+
- Configures startup/shutdown hooks
|
|
121
|
+
- Mounts routes
|
|
122
|
+
- Starts the ASGI server
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Key features:
|
|
126
|
+
- `get_application()`: Creates the main ASGI application instance
|
|
127
|
+
- `connect_db()`: Database connection setup on startup
|
|
128
|
+
- `disconnect_db()`: Clean database shutdown
|
|
129
|
+
- Uvicorn server configuration
|
|
130
|
+
|
|
131
|
+
### config/database.py
|
|
132
|
+
Database configuration and connection settings.
|
|
133
|
+
```python
|
|
134
|
+
# Key responsibilities:
|
|
135
|
+
- Defines database connection parameters
|
|
136
|
+
- Configures ORM settings
|
|
137
|
+
- Sets up model locations
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Key components:
|
|
141
|
+
- `TORTOISE_ORM`: ORM configuration dictionary
|
|
142
|
+
- Database URL configuration
|
|
143
|
+
- Model locations and namespacing
|
|
144
|
+
- Connection settings
|
|
145
|
+
|
|
146
|
+
### config/settings.py
|
|
147
|
+
Application-wide configuration and settings.
|
|
148
|
+
```python
|
|
149
|
+
# Key responsibilities:
|
|
150
|
+
- Defines core application settings
|
|
151
|
+
- Manages security configurations
|
|
152
|
+
- Sets environment-specific variables
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Key settings:
|
|
156
|
+
- `AppConfig`: Application configuration class
|
|
157
|
+
- `SECRET_KEY`: Security key for sessions/encryption
|
|
158
|
+
- Can be extended for additional settings:
|
|
159
|
+
- Debug modes
|
|
160
|
+
- API versions
|
|
161
|
+
- Environment configurations
|
|
162
|
+
|
|
163
|
+
### handlers/routes.py
|
|
164
|
+
HTTP request handlers and route definitions.
|
|
165
|
+
```python
|
|
166
|
+
# Key responsibilities:
|
|
167
|
+
- Defines endpoint handlers
|
|
168
|
+
- Processes HTTP requests
|
|
169
|
+
- Returns responses
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Key components:
|
|
173
|
+
- `home_handler`: Example route handler
|
|
174
|
+
- Request processing logic
|
|
175
|
+
- Response formatting
|
|
176
|
+
- URL parameter handling
|
|
177
|
+
|
|
178
|
+
## Usage Notes
|
|
179
|
+
|
|
180
|
+
1. **Configuration Priority**:
|
|
181
|
+
- `settings.py` loads first
|
|
182
|
+
- `database.py` uses settings for configuration
|
|
183
|
+
- `main.py` brings everything together
|
|
184
|
+
|
|
185
|
+
2. **Database Management**:
|
|
186
|
+
- Uses Tortoise ORM
|
|
187
|
+
- Automatic schema generation
|
|
188
|
+
- Connection lifecycle management
|
|
189
|
+
|
|
190
|
+
3. **Request Flow**:
|
|
191
|
+
```
|
|
192
|
+
Request → main.py → routes.py → handler → response
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
4. **Development Workflow**:
|
|
196
|
+
1. Modify settings in `config/`
|
|
197
|
+
2. Add routes in `handlers/routes.py`
|
|
198
|
+
3. Run application from `main.py`
|
|
199
|
+
|
|
200
|
+
## Common Extensions
|
|
201
|
+
|
|
202
|
+
The basic structure can be extended with:
|
|
203
|
+
|
|
204
|
+
1. **Additional Directories**:
|
|
205
|
+
- `models/`: Database models
|
|
206
|
+
- `middlewares/`: Custom middleware
|
|
207
|
+
- `schemas/`: Data validation
|
|
208
|
+
- `services/`: Business logic
|
|
209
|
+
|
|
210
|
+
2. **Configuration Files**:
|
|
211
|
+
- `logging.py`: Logging configuration
|
|
212
|
+
- `middleware.py`: Middleware settings
|
|
213
|
+
- `constants.py`: Application constants
|
|
214
|
+
|
|
215
|
+
## Best Practices
|
|
216
|
+
|
|
217
|
+
1. **Configuration**:
|
|
218
|
+
- Keep sensitive data in environment variables
|
|
219
|
+
- Use different settings for development/production
|
|
220
|
+
- Document all configuration options
|
|
221
|
+
|
|
222
|
+
2. **Route Handlers**:
|
|
223
|
+
- Keep handlers focused and simple
|
|
224
|
+
- Use type hints for better code clarity
|
|
225
|
+
- Return consistent response formats
|
|
226
|
+
|
|
227
|
+
3. **Database**:
|
|
228
|
+
- Use migrations for schema changes
|
|
229
|
+
- Implement proper connection pooling
|
|
230
|
+
- Handle connection errors gracefully
|
|
231
|
+
|
|
232
|
+
## Quick Start
|
|
233
|
+
```bash
|
|
234
|
+
# 1. Install dependencies
|
|
235
|
+
pip install git+https://github.com/TechWithDunamix/Nexio.git
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# 2. Run the application
|
|
239
|
+
uvicorn main:app --reload
|
|
240
|
+
|
|
241
|
+
# 3. Access the API
|
|
242
|
+
curl http://localhost:8000
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The application will be available at `http://localhost:8000` with:
|
|
246
|
+
- Database auto-configuration
|
|
247
|
+
- Basic route setup
|
|
248
|
+
- Error handling
|
|
249
|
+
- Clean shutdown support
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from .application import NexioApp
|
|
2
|
+
from .sessions.middlewares import SessionMiddleware
|
|
3
|
+
from .middlewares.logging import ErrorHandlerMiddleware
|
|
4
|
+
from .middlewares.common import CommonMiddleware
|
|
5
|
+
from .config.settings import BaseConfig
|
|
6
|
+
from .routers import Router
|
|
7
|
+
from .middlewares.cors import CORSMiddleware
|
|
8
|
+
import os
|
|
9
|
+
def get_application(config = BaseConfig) -> NexioApp:
|
|
10
|
+
config=config()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
app = NexioApp(
|
|
14
|
+
middlewares= [
|
|
15
|
+
ErrorHandlerMiddleware(),
|
|
16
|
+
CommonMiddleware(),
|
|
17
|
+
CORSMiddleware(
|
|
18
|
+
allow_origins=config.CORS_ALLOWED_ORIGINS,
|
|
19
|
+
blacklist_origins=config.CORS_BLACKLISTED_ORIGINS,
|
|
20
|
+
allow_methods=config.CORS_ALLOWED_METHODS,
|
|
21
|
+
allow_credentials=config.CORS_ALLOW_CREDENTIALS,
|
|
22
|
+
allow_headers=config.CORS_ALLOW_HEADERS,
|
|
23
|
+
expose_headers = config.EXPOSE_HEADERS,
|
|
24
|
+
allow_origin_regex=config.ALLOW_ORIGIN_REGEX
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
],
|
|
30
|
+
config=config
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return app
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from typing import Any, Callable, AsyncIterator, List, Union
|
|
2
|
+
from .http.request import Request
|
|
3
|
+
from .http.response import NexioResponse
|
|
4
|
+
from .http.response import JSONResponse
|
|
5
|
+
from .types import HTTPMethod
|
|
6
|
+
from .decorators import AllowedMethods
|
|
7
|
+
from .routers import Router, Routes
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from .config.settings import BaseConfig
|
|
10
|
+
import logging
|
|
11
|
+
from contextlib import asynccontextmanager
|
|
12
|
+
|
|
13
|
+
class NexioApp:
|
|
14
|
+
def __init__(self,
|
|
15
|
+
config: Enum = BaseConfig,
|
|
16
|
+
middlewares: list = None):
|
|
17
|
+
self.config = config
|
|
18
|
+
self.routes: List[str] = []
|
|
19
|
+
self.middlewares: List = middlewares or []
|
|
20
|
+
self.startup_handlers: List[Callable] = []
|
|
21
|
+
self.shutdown_handlers: List[Callable] = []
|
|
22
|
+
self.logger = logging.getLogger("nexio")
|
|
23
|
+
|
|
24
|
+
def on_startup(self, handler: Callable) -> Callable:
|
|
25
|
+
"""Decorator to register startup handlers"""
|
|
26
|
+
self.startup_handlers.append(handler)
|
|
27
|
+
return handler
|
|
28
|
+
|
|
29
|
+
def on_shutdown(self, handler: Callable) -> Callable:
|
|
30
|
+
"""Decorator to register shutdown handlers"""
|
|
31
|
+
self.shutdown_handlers.append(handler)
|
|
32
|
+
return handler
|
|
33
|
+
|
|
34
|
+
async def startup(self) -> None:
|
|
35
|
+
"""Execute all startup handlers sequentially"""
|
|
36
|
+
for handler in self.startup_handlers:
|
|
37
|
+
await handler()
|
|
38
|
+
|
|
39
|
+
async def shutdown(self) -> None:
|
|
40
|
+
"""Execute all shutdown handlers sequentially with error handling"""
|
|
41
|
+
for handler in self.shutdown_handlers:
|
|
42
|
+
try:
|
|
43
|
+
await handler()
|
|
44
|
+
except Exception as e:
|
|
45
|
+
self.logger.error(f"Shutdown handler error: {str(e)}")
|
|
46
|
+
|
|
47
|
+
async def handle_lifespan(self, receive: Callable, send: Callable) -> None:
|
|
48
|
+
"""Handle ASGI lifespan protocol events"""
|
|
49
|
+
try:
|
|
50
|
+
while True:
|
|
51
|
+
message = await receive()
|
|
52
|
+
|
|
53
|
+
if message["type"] == "lifespan.startup":
|
|
54
|
+
try:
|
|
55
|
+
await self.startup()
|
|
56
|
+
await send({"type": "lifespan.startup.complete"})
|
|
57
|
+
except Exception as e:
|
|
58
|
+
self.logger.error(f"Startup error: {str(e)}")
|
|
59
|
+
await send({"type": "lifespan.startup.failed", "message": str(e)})
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
elif message["type"] == "lifespan.shutdown":
|
|
63
|
+
try:
|
|
64
|
+
await self.shutdown()
|
|
65
|
+
await send({"type": "lifespan.shutdown.complete"})
|
|
66
|
+
return
|
|
67
|
+
except Exception as e:
|
|
68
|
+
self.logger.error(f"Shutdown error: {str(e)}")
|
|
69
|
+
await send({"type": "lifespan.shutdown.failed", "message": str(e)})
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
self.logger.error(f"Lifespan error: {str(e)}")
|
|
74
|
+
if message["type"].startswith("lifespan.startup"):
|
|
75
|
+
await send({"type": "lifespan.startup.failed", "message": str(e)})
|
|
76
|
+
else:
|
|
77
|
+
await send({"type": "lifespan.shutdown.failed", "message": str(e)})
|
|
78
|
+
|
|
79
|
+
async def execute_middleware_stack(self,
|
|
80
|
+
request: Request,
|
|
81
|
+
response: NexioResponse,
|
|
82
|
+
middleware: Callable,
|
|
83
|
+
handler: Callable,
|
|
84
|
+
**kwargs) -> Any:
|
|
85
|
+
stack = self.middlewares.copy()
|
|
86
|
+
if callable(middleware):
|
|
87
|
+
stack.append(middleware)
|
|
88
|
+
index = -1
|
|
89
|
+
|
|
90
|
+
async def next_middleware():
|
|
91
|
+
nonlocal index
|
|
92
|
+
index += 1
|
|
93
|
+
|
|
94
|
+
if index < len(stack):
|
|
95
|
+
middleware = stack[index]
|
|
96
|
+
return await middleware(request, response, next_middleware, **kwargs)
|
|
97
|
+
else:
|
|
98
|
+
return await handler(request, response, **kwargs)
|
|
99
|
+
|
|
100
|
+
return await next_middleware()
|
|
101
|
+
|
|
102
|
+
async def handle_request(self, scope: dict, receive: Callable, send: Callable) -> None:
|
|
103
|
+
request = Request(scope, receive, send)
|
|
104
|
+
response = NexioResponse()
|
|
105
|
+
request.scope['config'] = self.config
|
|
106
|
+
|
|
107
|
+
for path_pattern, handler, middleware in self.routes:
|
|
108
|
+
match = path_pattern.match(request.url.path)
|
|
109
|
+
if match:
|
|
110
|
+
kwargs = match.groupdict()
|
|
111
|
+
request.url_params = kwargs
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
await self.execute_middleware_stack(request,
|
|
115
|
+
response,
|
|
116
|
+
middleware,
|
|
117
|
+
handler)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
self.logger.error(f"Request handler error: {str(e)}")
|
|
120
|
+
error_response = JSONResponse(
|
|
121
|
+
{"error": str(e)},
|
|
122
|
+
status_code=500
|
|
123
|
+
)
|
|
124
|
+
await error_response(scope, receive, send)
|
|
125
|
+
return
|
|
126
|
+
await response(scope, receive, send)
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
error_response = JSONResponse({"error": "Not found"}, status_code=404)
|
|
130
|
+
await error_response(scope, receive, send)
|
|
131
|
+
|
|
132
|
+
def route(self, path: str, methods: List[Union[str, HTTPMethod]] = None) -> Callable:
|
|
133
|
+
"""Decorator to register routes with optional HTTP methods"""
|
|
134
|
+
def decorator(handler: Callable) -> Callable:
|
|
135
|
+
handler = AllowedMethods(methods)(handler)
|
|
136
|
+
self.add_route(Routes(path, handler))
|
|
137
|
+
return handler
|
|
138
|
+
return decorator
|
|
139
|
+
|
|
140
|
+
def add_route(self, route: Routes) -> None:
|
|
141
|
+
"""Add a route to the application"""
|
|
142
|
+
|
|
143
|
+
route, handler, middleware = route()
|
|
144
|
+
self.routes.append((route, handler, middleware))
|
|
145
|
+
|
|
146
|
+
def add_middleware(self, middleware: Callable) -> None:
|
|
147
|
+
"""Add middleware to the application"""
|
|
148
|
+
if callable(middleware):
|
|
149
|
+
self.middlewares.append(middleware)
|
|
150
|
+
|
|
151
|
+
def mount_router(self, router: Router) -> None:
|
|
152
|
+
"""Mount a router and all its routes to the application"""
|
|
153
|
+
for route in router.get_routes():
|
|
154
|
+
self.add_route(route)
|
|
155
|
+
|
|
156
|
+
async def __call__(self, scope: dict, receive: Callable, send: Callable) -> None:
|
|
157
|
+
"""ASGI application callable"""
|
|
158
|
+
if scope["type"] == "lifespan":
|
|
159
|
+
await self.handle_lifespan(receive, send)
|
|
160
|
+
elif scope["type"] == "http":
|
|
161
|
+
await self.handle_request(scope, receive, send)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
from nexios.utils.cuncurrency import run_in_threadpool
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import sys
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
if sys.version_info >= (3, 10):
|
|
8
|
+
from typing import ParamSpec
|
|
9
|
+
else:
|
|
10
|
+
from typing_extensions import ParamSpec
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
P = ParamSpec("P")
|
|
15
|
+
T = typing.TypeVar("T")
|
|
16
|
+
AwaitableCallable = typing.Callable[..., typing.Awaitable[T]]
|
|
17
|
+
|
|
18
|
+
@typing.overload
|
|
19
|
+
def is_async_callable(obj: AwaitableCallable[T]): ...
|
|
20
|
+
class BackgroundTask:
|
|
21
|
+
def __init__(self, func: typing.Callable[P, typing.Any], *args: P.args, **kwargs: P.kwargs) -> None:
|
|
22
|
+
self.func = func
|
|
23
|
+
self.args = args
|
|
24
|
+
self.kwargs = kwargs
|
|
25
|
+
self.is_async = is_async_callable(func)
|
|
26
|
+
|
|
27
|
+
async def __call__(self) -> None:
|
|
28
|
+
if self.is_async:
|
|
29
|
+
await self.func(*self.args, **self.kwargs)
|
|
30
|
+
else:
|
|
31
|
+
await run_in_threadpool(self.func, *self.args, **self.kwargs)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BackgroundTasks(BackgroundTask):
|
|
35
|
+
def __init__(self, tasks: typing.Sequence[BackgroundTask] | None = None):
|
|
36
|
+
self.tasks = list(tasks) if tasks else []
|
|
37
|
+
|
|
38
|
+
def add_task(self, func: typing.Callable[P, typing.Any], *args: P.args, **kwargs: P.kwargs) -> None:
|
|
39
|
+
task = BackgroundTask(func, *args, **kwargs)
|
|
40
|
+
self.tasks.append(task)
|
|
41
|
+
|
|
42
|
+
async def __call__(self) -> None:
|
|
43
|
+
for task in self.tasks:
|
|
44
|
+
await task()
|
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
def create_project_structure(project_name: str):
|
|
4
|
+
"""Create the basic project structure for a Nexio application."""
|
|
5
|
+
|
|
6
|
+
# Base directories
|
|
7
|
+
directories = [
|
|
8
|
+
f"{project_name}/controllers",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
# Create directories
|
|
12
|
+
for directory in directories:
|
|
13
|
+
os.makedirs(directory, exist_ok=True)
|
|
14
|
+
create_file(f"{directory}/__init__.py", "")
|
|
15
|
+
|
|
16
|
+
# Create main application files
|
|
17
|
+
create_file(f"{project_name}/main.py", main_code())
|
|
18
|
+
create_file(f"{project_name}/models.py", models_code())
|
|
19
|
+
create_file(f"{project_name}/settings.py", settings_code())
|
|
20
|
+
create_file(f"{project_name}/controllers/home_handler.py", routes_code())
|
|
21
|
+
create_file(f"{project_name}/README.md", readme_code())
|
|
22
|
+
|
|
23
|
+
print(f"\n✨ Created new Nexio project: {project_name}")
|
|
24
|
+
print("\nProject structure:")
|
|
25
|
+
print(f"""
|
|
26
|
+
{project_name}/
|
|
27
|
+
├── controllers/
|
|
28
|
+
│ └── home_handler.py
|
|
29
|
+
├── main.py
|
|
30
|
+
├── models.py
|
|
31
|
+
├── settings.py
|
|
32
|
+
└── README.md
|
|
33
|
+
""")
|
|
34
|
+
|
|
35
|
+
print("\nTo get started:")
|
|
36
|
+
print(f"1. cd {project_name}")
|
|
37
|
+
print("2. Install dependencies using `pip install -r requirements.txt`")
|
|
38
|
+
print("3. Run migrations using Aerich:")
|
|
39
|
+
print(" - `aerich init -t settings.TORTOISE_ORM`")
|
|
40
|
+
print(" - `aerich migrate`")
|
|
41
|
+
print(" - `aerich upgrade`")
|
|
42
|
+
print("4. Run the app with uvicorn:")
|
|
43
|
+
print(" - `uvicorn main:app --reload`")
|
|
44
|
+
print("\nYour app will be available at http://localhost:8000")
|
|
45
|
+
|
|
46
|
+
def create_file(file_path: str, content: str):
|
|
47
|
+
"""Create a file with the given content."""
|
|
48
|
+
with open(file_path, "w") as f:
|
|
49
|
+
f.write(content)
|
|
50
|
+
|
|
51
|
+
def main_code():
|
|
52
|
+
return '''import uvicorn
|
|
53
|
+
from nexio import get_application
|
|
54
|
+
from nexio.routers import Routes
|
|
55
|
+
from tortoise import Tortoise as db
|
|
56
|
+
from contextlib import asynccontextmanager
|
|
57
|
+
import traceback
|
|
58
|
+
import os
|
|
59
|
+
|
|
60
|
+
from settings import AppConfig
|
|
61
|
+
from controllers.home_handler import home_handler
|
|
62
|
+
|
|
63
|
+
# Initialize app
|
|
64
|
+
app = get_application(config=AppConfig)
|
|
65
|
+
|
|
66
|
+
@app.on_startup
|
|
67
|
+
async def connect_db():
|
|
68
|
+
try:
|
|
69
|
+
db_path = os.path.join(os.path.dirname(__file__), "db.sqlite3")
|
|
70
|
+
await db.init(
|
|
71
|
+
db_url=f"sqlite:///{db_path}",
|
|
72
|
+
modules={"models": ["models"]} # Use models.py directly
|
|
73
|
+
)
|
|
74
|
+
await db.generate_schemas()
|
|
75
|
+
print("Database connected")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"Database connection error: {e}")
|
|
78
|
+
print(traceback.format_exc())
|
|
79
|
+
|
|
80
|
+
@app.on_shutdown
|
|
81
|
+
async def disconnect_db():
|
|
82
|
+
try:
|
|
83
|
+
await db.close_connections()
|
|
84
|
+
print("Database disconnected")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f"Database disconnect error: {e}")
|
|
87
|
+
|
|
88
|
+
app.add_route(Routes("/", home_handler))
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
92
|
+
'''
|
|
93
|
+
|
|
94
|
+
def models_code():
|
|
95
|
+
return '''
|
|
96
|
+
'''
|
|
97
|
+
|
|
98
|
+
def settings_code():
|
|
99
|
+
return '''
|
|
100
|
+
import os
|
|
101
|
+
from nexio.config.settings import BaseConfig
|
|
102
|
+
|
|
103
|
+
migration = {
|
|
104
|
+
"connections": {
|
|
105
|
+
"default": f"sqlite://{os.path.join(os.path.dirname(__file__), 'database.db')}"
|
|
106
|
+
},
|
|
107
|
+
"apps": {
|
|
108
|
+
"models": {
|
|
109
|
+
"models": ["nexio.sessions.models","aerich.models"],
|
|
110
|
+
"default_connection": "default",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class AppConfig(BaseConfig):
|
|
116
|
+
SECRET_KEY = "your-secret-key" # Change this in production!
|
|
117
|
+
'''
|
|
118
|
+
|
|
119
|
+
def routes_code():
|
|
120
|
+
return '''from nexio.http.request import Request
|
|
121
|
+
from nexio.http.response import NexioResponse
|
|
122
|
+
|
|
123
|
+
async def home_handler(request: Request, response: NexioResponse, **kwargs):
|
|
124
|
+
return response.json({
|
|
125
|
+
"message": "Welcome to your new Nexio application!",
|
|
126
|
+
"docs": "https://nexio.example.com/docs"
|
|
127
|
+
})
|
|
128
|
+
'''
|
|
129
|
+
|
|
130
|
+
def readme_code():
|
|
131
|
+
return '''# Nexio Application Setup
|
|
132
|
+
|
|
133
|
+
## Requirements
|
|
134
|
+
- Python 3.8 or later
|
|
135
|
+
- [Aerich](https://github.com/tortoise/aerich) for migrations
|
|
136
|
+
- [Uvicorn](https://www.uvicorn.org/) for running the app
|
|
137
|
+
|
|
138
|
+
## Setup Instructions
|
|
139
|
+
|
|
140
|
+
1. Install the required dependencies:
|
|
141
|
+
```bash
|
|
142
|
+
|
|
143
|
+
Migrate the database: Run the following command to create the initial migration files:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
|
|
147
|
+
aerich init -t settings.migration
|
|
148
|
+
Then, run the following command to apply the migrations to the database:
|
|
149
|
+
```
|
|
150
|
+
bash
|
|
151
|
+
```
|
|
152
|
+
aerich migrate
|
|
153
|
+
aerich upgrade
|
|
154
|
+
```
|
|
155
|
+
To start the application, use Uvicorn:
|
|
156
|
+
|
|
157
|
+
``bash
|
|
158
|
+
|
|
159
|
+
uvicorn main:app --reload
|
|
160
|
+
```
|
|
161
|
+
'''
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from nexios.cli.create_project import create_project_structure
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
parser = argparse.ArgumentParser(prog="nexio")
|
|
6
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
7
|
+
|
|
8
|
+
# 'create' subcommand
|
|
9
|
+
create_parser = subparsers.add_parser('create', help="Create a new Nexio project")
|
|
10
|
+
create_parser.add_argument('project_name', type=str, help="The name of the project to create")
|
|
11
|
+
|
|
12
|
+
args = parser.parse_args()
|
|
13
|
+
|
|
14
|
+
if args.command == "create":
|
|
15
|
+
create_project_structure(args.project_name)
|
|
16
|
+
else:
|
|
17
|
+
parser.print_help()
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .settings import BaseConfig
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseConfig:
|
|
6
|
+
|
|
7
|
+
debug :bool = False
|
|
8
|
+
|
|
9
|
+
middleware :list = []
|
|
10
|
+
|
|
11
|
+
COOKIE_AGE = 259200
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __getattribute__(self, name: str) -> Any:
|
|
15
|
+
try:
|
|
16
|
+
return super().__getattribute__(name)
|
|
17
|
+
|
|
18
|
+
except AttributeError:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|