neutronapi 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.
Files changed (46) hide show
  1. neutronapi-0.1.0/LICENSE +21 -0
  2. neutronapi-0.1.0/MANIFEST.in +7 -0
  3. neutronapi-0.1.0/PKG-INFO +202 -0
  4. neutronapi-0.1.0/README.md +170 -0
  5. neutronapi-0.1.0/neutronapi/__init__.py +21 -0
  6. neutronapi-0.1.0/neutronapi/application.py +113 -0
  7. neutronapi-0.1.0/neutronapi/authentication/__init__.py +9 -0
  8. neutronapi-0.1.0/neutronapi/authentication/base.py +34 -0
  9. neutronapi-0.1.0/neutronapi/background.py +400 -0
  10. neutronapi-0.1.0/neutronapi/base.py +914 -0
  11. neutronapi-0.1.0/neutronapi/cli.py +135 -0
  12. neutronapi-0.1.0/neutronapi/commands/__init__.py +2 -0
  13. neutronapi-0.1.0/neutronapi/commands/migrate.py +89 -0
  14. neutronapi-0.1.0/neutronapi/commands/shell.py +105 -0
  15. neutronapi-0.1.0/neutronapi/commands/start.py +219 -0
  16. neutronapi-0.1.0/neutronapi/commands/startapp.py +50 -0
  17. neutronapi-0.1.0/neutronapi/commands/startproject.py +87 -0
  18. neutronapi-0.1.0/neutronapi/commands/test.py +336 -0
  19. neutronapi-0.1.0/neutronapi/db/__init__.py +33 -0
  20. neutronapi-0.1.0/neutronapi/db/connection.py +138 -0
  21. neutronapi-0.1.0/neutronapi/db/fields.py +915 -0
  22. neutronapi-0.1.0/neutronapi/db/migrations.py +1286 -0
  23. neutronapi-0.1.0/neutronapi/db/models.py +147 -0
  24. neutronapi-0.1.0/neutronapi/db/providers/__init__.py +26 -0
  25. neutronapi-0.1.0/neutronapi/db/providers/base.py +44 -0
  26. neutronapi-0.1.0/neutronapi/db/providers/postgres.py +343 -0
  27. neutronapi-0.1.0/neutronapi/db/providers/sqlite.py +454 -0
  28. neutronapi-0.1.0/neutronapi/db/queryset.py +925 -0
  29. neutronapi-0.1.0/neutronapi/encoders.py +14 -0
  30. neutronapi-0.1.0/neutronapi/exceptions.py +21 -0
  31. neutronapi-0.1.0/neutronapi/middleware/__init__.py +0 -0
  32. neutronapi-0.1.0/neutronapi/middleware/allowed_hosts.py +116 -0
  33. neutronapi-0.1.0/neutronapi/middleware/cors.py +108 -0
  34. neutronapi-0.1.0/neutronapi/middleware/routing.py +211 -0
  35. neutronapi-0.1.0/neutronapi/multipart.py +377 -0
  36. neutronapi-0.1.0/neutronapi/openapi/__init__.py +0 -0
  37. neutronapi-0.1.0/neutronapi/openapi/openapi.py +805 -0
  38. neutronapi-0.1.0/neutronapi/openapi/swagger.py +457 -0
  39. neutronapi-0.1.0/neutronapi.egg-info/PKG-INFO +202 -0
  40. neutronapi-0.1.0/neutronapi.egg-info/SOURCES.txt +44 -0
  41. neutronapi-0.1.0/neutronapi.egg-info/dependency_links.txt +1 -0
  42. neutronapi-0.1.0/neutronapi.egg-info/entry_points.txt +2 -0
  43. neutronapi-0.1.0/neutronapi.egg-info/requires.txt +11 -0
  44. neutronapi-0.1.0/neutronapi.egg-info/top_level.txt +1 -0
  45. neutronapi-0.1.0/pyproject.toml +48 -0
  46. neutronapi-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aaron Kazah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ recursive-include neutronapi *.py
5
+ recursive-exclude neutronapi/tests *
6
+ recursive-exclude apps *
7
+ exclude manage.py
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: neutronapi
3
+ Version: 0.1.0
4
+ Summary: High-performance Python framework built directly on uvicorn with built-in database models, migrations, and background tasks. Django that was built async-first.
5
+ Author-email: Aaron Kazah <aaron@neutronapi.com>
6
+ License-Expression: MIT
7
+ Keywords: api,framework,async,django,fastapi,uvicorn,orm,migrations,background-tasks
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Framework :: AsyncIO
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: uvicorn[standard]>=0.23.0
23
+ Requires-Dist: aiosqlite>=0.19.0
24
+ Requires-Dist: PyJWT>=2.8.0
25
+ Provides-Extra: postgres
26
+ Requires-Dist: asyncpg>=0.29.0; extra == "postgres"
27
+ Provides-Extra: dev
28
+ Requires-Dist: httpx>=0.24.0; extra == "dev"
29
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # NeutronAPI
34
+
35
+ **High-performance Python framework built directly on uvicorn with built-in database models, migrations, and background tasks. If you want Django that was built async-first, this is for you.**
36
+
37
+ Batteries included async API framework with command-line management.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install neutronapi
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```bash
48
+ # 1. Create project
49
+ neutronapi startproject blog
50
+ cd blog
51
+
52
+ # 2. Create an app
53
+ python manage.py startapp posts
54
+
55
+ # 3. Start server
56
+ python manage.py start # Dev mode (auto-reload)
57
+
58
+ # 4. Test
59
+ python manage.py test
60
+ ```
61
+
62
+ ## Getting Started Tutorial
63
+
64
+ **1. Create Project**
65
+ ```bash
66
+ neutronapi startproject blog
67
+ cd blog
68
+ ```
69
+
70
+ **2. Create App Module**
71
+ ```bash
72
+ python manage.py startapp posts
73
+ ```
74
+
75
+ **3. Configure in `apps/settings.py`**
76
+ ```python
77
+ import os
78
+
79
+ # ASGI application entry point (required for server)
80
+ ENTRY = "apps.entry:app" # module:variable format
81
+
82
+ # Database
83
+ DATABASES = {
84
+ 'default': {
85
+ 'ENGINE': 'aiosqlite',
86
+ 'NAME': 'db.sqlite3',
87
+ }
88
+ }
89
+ ```
90
+
91
+ **4. Create API in `apps/posts/api.py`**
92
+ ```python
93
+ from neutronapi.base import API
94
+
95
+ class PostAPI(API):
96
+ name = "posts"
97
+
98
+ @API.endpoint("/posts", methods=["GET"])
99
+ async def list_posts(self, scope, receive, send, **kwargs):
100
+ posts = [{"id": 1, "title": "Hello World"}]
101
+ return await self.response(posts)
102
+
103
+ @API.endpoint("/posts", methods=["POST"])
104
+ async def create_post(self, scope, receive, send, **kwargs):
105
+ # Get request data from scope["body"]
106
+ return await self.response({"id": 2, "title": "New Post"})
107
+ ```
108
+
109
+ **5. Register API in `apps/entry.py`**
110
+ ```python
111
+ from neutronapi.application import Application
112
+ from apps.posts.api import PostAPI
113
+
114
+ app = Application({
115
+ "posts": PostAPI()
116
+ })
117
+ ```
118
+
119
+ **6. Start Server**
120
+ ```bash
121
+ python manage.py start
122
+ # Visit: http://127.0.0.1:8000/posts
123
+ ```
124
+
125
+ ## Project Structure
126
+
127
+ ```
128
+ myproject/
129
+ ├── manage.py # Management commands
130
+ ├── apps/
131
+ │ ├── __init__.py
132
+ │ ├── settings.py # Configuration
133
+ │ └── entry.py # ASGI application
134
+ └── db.sqlite3 # Database
135
+ ```
136
+
137
+ ## Background Tasks
138
+
139
+ ```python
140
+ from neutronapi.background import Task, TaskFrequency
141
+
142
+ class CleanupTask(Task):
143
+ name = "cleanup"
144
+ frequency = TaskFrequency.MINUTELY
145
+
146
+ async def run(self, **kwargs):
147
+ print("Cleaning up logs...")
148
+
149
+ # Add to application
150
+ app = Application(
151
+ apis={"ping": PingAPI()},
152
+ tasks={"cleanup": CleanupTask()}
153
+ )
154
+
155
+ ## Database Models
156
+
157
+ ```python
158
+ from neutronapi.db.models import Model
159
+ from neutronapi.db.fields import CharField, IntegerField, DateTimeField
160
+
161
+ class User(Model):
162
+ name = CharField(max_length=100)
163
+ age = IntegerField()
164
+ created_at = DateTimeField(auto_now_add=True)
165
+
166
+ # Table name inferred from class name (user -> users)
167
+ ```
168
+
169
+ ## Server Commands
170
+
171
+ ```bash
172
+ # Development (auto-reload, localhost)
173
+ python manage.py start
174
+
175
+ # Production (multi-worker, optimized)
176
+ python manage.py start --production
177
+
178
+ # Custom configuration
179
+ python manage.py start --host 0.0.0.0 --port 8080 --workers 4
180
+ ```
181
+
182
+ ## Testing
183
+
184
+ ```bash
185
+ # SQLite (default)
186
+ python manage.py test
187
+
188
+ # PostgreSQL (auto Docker bootstrap)
189
+ DATABASE_PROVIDER=asyncpg python manage.py test
190
+
191
+ # Specific tests
192
+ python manage.py test app.tests.test_models.TestUser.test_creation
193
+ ```
194
+
195
+ ## Commands
196
+
197
+ ```bash
198
+ python manage.py start # Start server
199
+ python manage.py test # Run tests
200
+ python manage.py migrate # Run migrations
201
+ python manage.py startapp posts # Create new app
202
+ ```
@@ -0,0 +1,170 @@
1
+ # NeutronAPI
2
+
3
+ **High-performance Python framework built directly on uvicorn with built-in database models, migrations, and background tasks. If you want Django that was built async-first, this is for you.**
4
+
5
+ Batteries included async API framework with command-line management.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install neutronapi
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # 1. Create project
17
+ neutronapi startproject blog
18
+ cd blog
19
+
20
+ # 2. Create an app
21
+ python manage.py startapp posts
22
+
23
+ # 3. Start server
24
+ python manage.py start # Dev mode (auto-reload)
25
+
26
+ # 4. Test
27
+ python manage.py test
28
+ ```
29
+
30
+ ## Getting Started Tutorial
31
+
32
+ **1. Create Project**
33
+ ```bash
34
+ neutronapi startproject blog
35
+ cd blog
36
+ ```
37
+
38
+ **2. Create App Module**
39
+ ```bash
40
+ python manage.py startapp posts
41
+ ```
42
+
43
+ **3. Configure in `apps/settings.py`**
44
+ ```python
45
+ import os
46
+
47
+ # ASGI application entry point (required for server)
48
+ ENTRY = "apps.entry:app" # module:variable format
49
+
50
+ # Database
51
+ DATABASES = {
52
+ 'default': {
53
+ 'ENGINE': 'aiosqlite',
54
+ 'NAME': 'db.sqlite3',
55
+ }
56
+ }
57
+ ```
58
+
59
+ **4. Create API in `apps/posts/api.py`**
60
+ ```python
61
+ from neutronapi.base import API
62
+
63
+ class PostAPI(API):
64
+ name = "posts"
65
+
66
+ @API.endpoint("/posts", methods=["GET"])
67
+ async def list_posts(self, scope, receive, send, **kwargs):
68
+ posts = [{"id": 1, "title": "Hello World"}]
69
+ return await self.response(posts)
70
+
71
+ @API.endpoint("/posts", methods=["POST"])
72
+ async def create_post(self, scope, receive, send, **kwargs):
73
+ # Get request data from scope["body"]
74
+ return await self.response({"id": 2, "title": "New Post"})
75
+ ```
76
+
77
+ **5. Register API in `apps/entry.py`**
78
+ ```python
79
+ from neutronapi.application import Application
80
+ from apps.posts.api import PostAPI
81
+
82
+ app = Application({
83
+ "posts": PostAPI()
84
+ })
85
+ ```
86
+
87
+ **6. Start Server**
88
+ ```bash
89
+ python manage.py start
90
+ # Visit: http://127.0.0.1:8000/posts
91
+ ```
92
+
93
+ ## Project Structure
94
+
95
+ ```
96
+ myproject/
97
+ ├── manage.py # Management commands
98
+ ├── apps/
99
+ │ ├── __init__.py
100
+ │ ├── settings.py # Configuration
101
+ │ └── entry.py # ASGI application
102
+ └── db.sqlite3 # Database
103
+ ```
104
+
105
+ ## Background Tasks
106
+
107
+ ```python
108
+ from neutronapi.background import Task, TaskFrequency
109
+
110
+ class CleanupTask(Task):
111
+ name = "cleanup"
112
+ frequency = TaskFrequency.MINUTELY
113
+
114
+ async def run(self, **kwargs):
115
+ print("Cleaning up logs...")
116
+
117
+ # Add to application
118
+ app = Application(
119
+ apis={"ping": PingAPI()},
120
+ tasks={"cleanup": CleanupTask()}
121
+ )
122
+
123
+ ## Database Models
124
+
125
+ ```python
126
+ from neutronapi.db.models import Model
127
+ from neutronapi.db.fields import CharField, IntegerField, DateTimeField
128
+
129
+ class User(Model):
130
+ name = CharField(max_length=100)
131
+ age = IntegerField()
132
+ created_at = DateTimeField(auto_now_add=True)
133
+
134
+ # Table name inferred from class name (user -> users)
135
+ ```
136
+
137
+ ## Server Commands
138
+
139
+ ```bash
140
+ # Development (auto-reload, localhost)
141
+ python manage.py start
142
+
143
+ # Production (multi-worker, optimized)
144
+ python manage.py start --production
145
+
146
+ # Custom configuration
147
+ python manage.py start --host 0.0.0.0 --port 8080 --workers 4
148
+ ```
149
+
150
+ ## Testing
151
+
152
+ ```bash
153
+ # SQLite (default)
154
+ python manage.py test
155
+
156
+ # PostgreSQL (auto Docker bootstrap)
157
+ DATABASE_PROVIDER=asyncpg python manage.py test
158
+
159
+ # Specific tests
160
+ python manage.py test app.tests.test_models.TestUser.test_creation
161
+ ```
162
+
163
+ ## Commands
164
+
165
+ ```bash
166
+ python manage.py start # Start server
167
+ python manage.py test # Run tests
168
+ python manage.py migrate # Run migrations
169
+ python manage.py startapp posts # Create new app
170
+ ```
@@ -0,0 +1,21 @@
1
+ """NeutronAPI - High-performance Python framework built directly on uvicorn.
2
+
3
+ If you want Django that was built async-first, this is for you.
4
+ """
5
+
6
+ __version__ = "0.1.0"
7
+
8
+ from .base import API, Response, Endpoint
9
+ from .application import Application
10
+ from .background import Background, Task, TaskFrequency, TaskPriority
11
+
12
+ __all__ = [
13
+ 'API',
14
+ 'Response',
15
+ 'Endpoint',
16
+ 'Application',
17
+ 'Background',
18
+ 'Task',
19
+ 'TaskFrequency',
20
+ 'TaskPriority',
21
+ ]
@@ -0,0 +1,113 @@
1
+ from typing import Dict, Optional, Callable, List, Any
2
+ import asyncio
3
+
4
+ from neutronapi.base import API
5
+ from neutronapi.middleware.cors import CORS
6
+ from neutronapi.middleware.routing import RoutingMiddleware
7
+ from neutronapi.middleware.allowed_hosts import AllowedHostsMiddleware
8
+ from neutronapi.background import Background, TaskFrequency, TaskPriority
9
+
10
+
11
+ class Application:
12
+ """ASGI application that composes APIs + middleware + optional background tasks.
13
+
14
+ Prefer using this class directly. The create_application() helper remains for compatibility.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ apis: Dict[str, API],
20
+ *,
21
+ tasks: Optional[Dict[str, Any]] = None,
22
+ version: str = "1.0.0",
23
+ allowed_hosts: Optional[List[str]] = None,
24
+ static_hosts: Optional[List[str]] = None,
25
+ static_resolver: Optional[Callable] = None,
26
+ cors_allow_all: bool = True,
27
+ ) -> None:
28
+ self.apis = apis
29
+ self.version = version
30
+
31
+ # Simple handler that routes to APIs
32
+ async def app(scope, receive, send):
33
+ if scope["type"] == "http":
34
+ path = scope.get("path", "/").lstrip("/")
35
+
36
+ # Check if path matches any API
37
+ if path in self.apis:
38
+ api = self.apis[path]
39
+ await api.handle(scope, receive, send)
40
+ return
41
+
42
+ # Default 404 for unmatched paths
43
+ await send({
44
+ "type": "http.response.start",
45
+ "status": 404,
46
+ "headers": [[b"content-type", b"text/plain"]],
47
+ })
48
+ await send({
49
+ "type": "http.response.body",
50
+ "body": b"Not Found",
51
+ })
52
+
53
+ # Skip hosts middleware if no allowed_hosts specified (for testing)
54
+ if allowed_hosts:
55
+ hosts_app = AllowedHostsMiddleware(app, allowed_hosts=allowed_hosts)
56
+ else:
57
+ hosts_app = app
58
+ cors_wrapped = CORS(hosts_app, allow_all_origins=cors_allow_all)
59
+ self.app = RoutingMiddleware(
60
+ default_app=cors_wrapped,
61
+ static_hosts=static_hosts,
62
+ static_resolver=static_resolver,
63
+ )
64
+
65
+ # lifecycle hooks
66
+ self.app.on_startup = []
67
+ self.app.on_shutdown = []
68
+
69
+ # Expose lifecycle hooks on Application instance for compatibility
70
+ self.on_startup = self.app.on_startup
71
+ self.on_shutdown = self.app.on_shutdown
72
+
73
+ # Handle tasks dict - clean API-like pattern
74
+ if tasks:
75
+ from neutronapi.background import Background
76
+ self.background = Background()
77
+
78
+ # Register all tasks
79
+ for name, task in tasks.items():
80
+ self.background.register_task(task)
81
+
82
+ async def _start_background():
83
+ await self.background.start()
84
+
85
+ async def _stop_background():
86
+ await self.background.stop()
87
+
88
+ self.app.on_startup.append(_start_background)
89
+ self.app.on_shutdown.append(_stop_background)
90
+
91
+ async def __call__(self, scope, receive, send, **kwargs):
92
+ return await self.app(scope, receive, send, **kwargs)
93
+
94
+
95
+ def create_application(
96
+ apis: Dict[str, API],
97
+ static_hosts: Optional[List[str]] = None,
98
+ static_resolver: Optional[Callable] = None,
99
+ allowed_hosts: Optional[List[str]] = None,
100
+ version: str = "1.0.0",
101
+ expose_docs: bool = False, # kept for compatibility; no-op
102
+ ):
103
+ """Compatibility wrapper that returns an Application instance.
104
+
105
+ Docs are not injected automatically; pass your own docs API if desired.
106
+ """
107
+ return Application(
108
+ apis,
109
+ version=version,
110
+ allowed_hosts=allowed_hosts,
111
+ static_hosts=static_hosts,
112
+ static_resolver=static_resolver,
113
+ )
@@ -0,0 +1,9 @@
1
+ __all__ = [
2
+ "JWTAuthentication",
3
+ "Authentication",
4
+ "Encryption",
5
+ ]
6
+
7
+ from .base import Authentication
8
+ from .jwt import JWTAuthentication
9
+ from .encryption import Encryption
@@ -0,0 +1,34 @@
1
+ import bcrypt
2
+ import asyncio
3
+ from typing import Any, List, Optional
4
+ import abc
5
+
6
+
7
+ class Authentication(abc.ABC):
8
+ @classmethod
9
+ @abc.abstractmethod
10
+ async def authenticate(cls, email: str, password: str) -> Optional[Any]:
11
+ raise NotImplementedError("Subclasses must implement authenticate")
12
+
13
+ @classmethod
14
+ @abc.abstractmethod
15
+ async def authorize(cls, scope: List[str]) -> bool:
16
+ raise NotImplementedError("Subclasses must implement authorize")
17
+
18
+ @staticmethod
19
+ async def hash_password(password: str) -> str:
20
+ def _hash():
21
+ salt = bcrypt.gensalt()
22
+ hashed = bcrypt.hashpw(password.encode("utf-8"), salt)
23
+ return hashed.decode("utf-8")
24
+
25
+ return await asyncio.to_thread(_hash)
26
+
27
+ @staticmethod
28
+ async def check_password(hashed_password: str, plain_password: str) -> bool:
29
+ def _check():
30
+ return bcrypt.checkpw(
31
+ plain_password.encode("utf-8"), hashed_password.encode("utf-8")
32
+ )
33
+
34
+ return await asyncio.to_thread(_check)