Plinx 0.0.1__py3-none-any.whl → 1.0.1__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.
- plinx/__init__.py +2 -0
- plinx/applications.py +212 -28
- plinx/methods.py +61 -2
- plinx/middleware.py +99 -22
- plinx/orm/__init__.py +1 -0
- plinx/orm/orm.py +550 -0
- plinx/orm/utils.py +7 -0
- plinx/response.py +78 -6
- plinx/status_codes.py +26 -5
- plinx/utils.py +18 -3
- {Plinx-0.0.1.dist-info → plinx-1.0.1.dist-info}/METADATA +42 -22
- plinx-1.0.1.dist-info/RECORD +15 -0
- {Plinx-0.0.1.dist-info → plinx-1.0.1.dist-info}/WHEEL +1 -1
- Plinx-0.0.1.dist-info/RECORD +0 -12
- {Plinx-0.0.1.dist-info → plinx-1.0.1.dist-info/licenses}/LICENSE +0 -0
- {Plinx-0.0.1.dist-info → plinx-1.0.1.dist-info}/top_level.txt +0 -0
plinx/__init__.py
CHANGED
plinx/applications.py
CHANGED
@@ -15,7 +15,58 @@ from .utils import handle_404
|
|
15
15
|
|
16
16
|
|
17
17
|
class Plinx:
|
18
|
+
"""
|
19
|
+
The main application class for the Plinx web framework.
|
20
|
+
|
21
|
+
This class serves as the WSGI application entry point and manages routes,
|
22
|
+
middleware, and request handling. It provides a Flask-like decorator syntax
|
23
|
+
for adding routes and Django-like method for explicitly registering them.
|
24
|
+
|
25
|
+
Examples:
|
26
|
+
Creating a simple app with a route:
|
27
|
+
|
28
|
+
```python
|
29
|
+
from plinx import Plinx
|
30
|
+
|
31
|
+
app = Plinx()
|
32
|
+
|
33
|
+
@app.route("/")
|
34
|
+
def home(request, response):
|
35
|
+
response.text = "Hello, World!"
|
36
|
+
```
|
37
|
+
|
38
|
+
Using HTTP method-specific decorators:
|
39
|
+
|
40
|
+
```python
|
41
|
+
@app.get("/users")
|
42
|
+
def list_users(request, response):
|
43
|
+
response.json = {"users": ["user1", "user2"]}
|
44
|
+
|
45
|
+
@app.post("/users")
|
46
|
+
def create_user(request, response):
|
47
|
+
response.text = "User created"
|
48
|
+
```
|
49
|
+
|
50
|
+
Using class-based views:
|
51
|
+
|
52
|
+
```python
|
53
|
+
@app.route("/books")
|
54
|
+
class BooksResource:
|
55
|
+
def get(self, request, response):
|
56
|
+
response.text = "List of books"
|
57
|
+
|
58
|
+
def post(self, request, response):
|
59
|
+
response.text = "Book created"
|
60
|
+
```
|
61
|
+
"""
|
62
|
+
|
18
63
|
def __init__(self):
|
64
|
+
"""
|
65
|
+
Initialize a new Plinx application instance.
|
66
|
+
|
67
|
+
Sets up the routing table, middleware stack, and
|
68
|
+
dynamically generates HTTP method-specific decorators.
|
69
|
+
"""
|
19
70
|
self.routes: Dict[str, Tuple[HTTPMethods, Callable]] = {}
|
20
71
|
self.exception_handler = None
|
21
72
|
self.middleware = Middleware(self)
|
@@ -32,10 +83,17 @@ class Plinx:
|
|
32
83
|
start_response: StartResponse,
|
33
84
|
) -> Iterable[bytes]:
|
34
85
|
"""
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
86
|
+
WSGI entry point for the application.
|
87
|
+
|
88
|
+
This method makes the Plinx instance callable as required by the WSGI spec,
|
89
|
+
allowing it to be used directly with WSGI servers like Gunicorn or uWSGI.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
environ: The WSGI environment dictionary containing request information
|
93
|
+
start_response: The WSGI start_response callable
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
An iterable of bytes representing the response body
|
39
97
|
"""
|
40
98
|
return self.middleware(environ, start_response)
|
41
99
|
|
@@ -46,10 +104,26 @@ class Plinx:
|
|
46
104
|
method: HTTPMethods = HTTPMethods.GET,
|
47
105
|
):
|
48
106
|
"""
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
107
|
+
Explicitly register a route with the application.
|
108
|
+
|
109
|
+
This provides a Django-like syntax for registering routes,
|
110
|
+
as an alternative to the decorator approach.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
path: URL pattern to match (may contain parameters)
|
114
|
+
handler: Function or class to handle matching requests
|
115
|
+
method: HTTP method to match (defaults to GET)
|
116
|
+
|
117
|
+
Raises:
|
118
|
+
RuntimeError: If the path is already registered
|
119
|
+
|
120
|
+
Example:
|
121
|
+
```python
|
122
|
+
def home(request, response):
|
123
|
+
response.text = "Hello, World!"
|
124
|
+
|
125
|
+
app.add_route("/home", home)
|
126
|
+
```
|
53
127
|
"""
|
54
128
|
if path in self.routes:
|
55
129
|
raise RuntimeError(f"Route '{path}' is already registered.")
|
@@ -61,9 +135,32 @@ class Plinx:
|
|
61
135
|
path: str,
|
62
136
|
):
|
63
137
|
"""
|
64
|
-
Register a route
|
65
|
-
|
66
|
-
|
138
|
+
Register a route via decorator syntax.
|
139
|
+
|
140
|
+
This implements Flask-like syntax for registering routes. It can be used
|
141
|
+
with both function-based handlers and class-based handlers.
|
142
|
+
|
143
|
+
Args:
|
144
|
+
path: URL pattern to match (may contain parameters)
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
A decorator function that registers the handler
|
148
|
+
|
149
|
+
Example:
|
150
|
+
```python
|
151
|
+
@app.route("/home")
|
152
|
+
def home(request, response):
|
153
|
+
response.text = "Hello, World!"
|
154
|
+
```
|
155
|
+
|
156
|
+
For class-based views:
|
157
|
+
|
158
|
+
```python
|
159
|
+
@app.route("/books")
|
160
|
+
class BooksResource:
|
161
|
+
def get(self, request, response):
|
162
|
+
response.text = "List of books"
|
163
|
+
```
|
67
164
|
"""
|
68
165
|
|
69
166
|
def wrapper(handler):
|
@@ -76,7 +173,21 @@ class Plinx:
|
|
76
173
|
self,
|
77
174
|
name: str,
|
78
175
|
):
|
79
|
-
"""
|
176
|
+
"""
|
177
|
+
Enable HTTP method-specific decorators like app.get, app.post, etc.
|
178
|
+
|
179
|
+
This magic method is called when an attribute lookup fails, allowing
|
180
|
+
us to dynamically provide HTTP method decorators.
|
181
|
+
|
182
|
+
Args:
|
183
|
+
name: The attribute name being looked up
|
184
|
+
|
185
|
+
Returns:
|
186
|
+
A method-specific decorator function if name matches a HTTP method
|
187
|
+
|
188
|
+
Raises:
|
189
|
+
RuntimeError: If the attribute doesn't match a known HTTP method
|
190
|
+
"""
|
80
191
|
if name in self._method_decorators:
|
81
192
|
return self._method_decorators[name]
|
82
193
|
raise RuntimeError(
|
@@ -85,9 +196,16 @@ class Plinx:
|
|
85
196
|
|
86
197
|
def _create_method_decorator(self, method: HTTPMethods):
|
87
198
|
"""
|
88
|
-
|
89
|
-
|
90
|
-
|
199
|
+
Create a decorator for a specific HTTP method.
|
200
|
+
|
201
|
+
This internal method generates the decorators used for HTTP method-specific
|
202
|
+
route registration like @app.get(), @app.post(), etc.
|
203
|
+
|
204
|
+
Args:
|
205
|
+
method: The HTTP method enum value
|
206
|
+
|
207
|
+
Returns:
|
208
|
+
A decorator function for the specified HTTP method
|
91
209
|
"""
|
92
210
|
|
93
211
|
def decorator(path: str):
|
@@ -104,9 +222,16 @@ class Plinx:
|
|
104
222
|
request: Request,
|
105
223
|
) -> Response:
|
106
224
|
"""
|
107
|
-
|
108
|
-
|
109
|
-
|
225
|
+
Process an incoming request and generate a response.
|
226
|
+
|
227
|
+
This is the core request handling logic that finds a matching route handler,
|
228
|
+
executes it, and handles any exceptions.
|
229
|
+
|
230
|
+
Args:
|
231
|
+
request: The incoming WebOb Request object
|
232
|
+
|
233
|
+
Returns:
|
234
|
+
A Response object containing the response data
|
110
235
|
"""
|
111
236
|
response: Response = Response()
|
112
237
|
|
@@ -151,16 +276,21 @@ class Plinx:
|
|
151
276
|
self,
|
152
277
|
request: Request,
|
153
278
|
response: Response,
|
154
|
-
) -> Tuple[
|
279
|
+
) -> Tuple[Tuple[HTTPMethods, Callable] | None, dict | None]:
|
155
280
|
"""
|
156
|
-
Find the handler for
|
157
|
-
|
158
|
-
and
|
281
|
+
Find the appropriate handler for a request based on URL path matching.
|
282
|
+
|
283
|
+
This method iterates through registered routes and uses the parse library
|
284
|
+
to match URL patterns and extract parameters.
|
159
285
|
|
160
|
-
:
|
161
|
-
|
162
|
-
|
163
|
-
|
286
|
+
Args:
|
287
|
+
request: The incoming WebOb Request object
|
288
|
+
response: The Response object being built
|
289
|
+
|
290
|
+
Returns:
|
291
|
+
A tuple containing:
|
292
|
+
- The handler definition (method, handler) if found, or None
|
293
|
+
- A dictionary of extracted URL parameters, or None
|
164
294
|
"""
|
165
295
|
for path, handler in self.routes.items():
|
166
296
|
parse_result = parse(path, request.path)
|
@@ -174,6 +304,24 @@ class Plinx:
|
|
174
304
|
self,
|
175
305
|
exception_handler,
|
176
306
|
):
|
307
|
+
"""
|
308
|
+
Register a global exception handler for the application.
|
309
|
+
|
310
|
+
The exception handler will be called whenever an uncaught exception
|
311
|
+
occurs during request handling.
|
312
|
+
|
313
|
+
Args:
|
314
|
+
exception_handler: Callable that takes (request, response, exception)
|
315
|
+
|
316
|
+
Example:
|
317
|
+
```python
|
318
|
+
def handle_exceptions(request, response, exception):
|
319
|
+
response.status_code = 500
|
320
|
+
response.text = f"Error: {str(exception)}"
|
321
|
+
|
322
|
+
app.add_exception_handler(handle_exceptions)
|
323
|
+
```
|
324
|
+
"""
|
177
325
|
self.exception_handler = exception_handler
|
178
326
|
|
179
327
|
def add_middleware(
|
@@ -181,12 +329,48 @@ class Plinx:
|
|
181
329
|
middleware_cls: type[Middleware],
|
182
330
|
):
|
183
331
|
"""
|
184
|
-
Add a middleware class to the application.
|
185
|
-
|
332
|
+
Add a middleware class to the application's middleware stack.
|
333
|
+
|
334
|
+
Middleware classes must inherit from the Middleware base class and can
|
335
|
+
process requests before they reach handlers and responses before they're returned.
|
336
|
+
|
337
|
+
Args:
|
338
|
+
middleware_cls: A class inheriting from Middleware
|
339
|
+
|
340
|
+
Example:
|
341
|
+
```python
|
342
|
+
class SimpleMiddleware(Middleware):
|
343
|
+
def process_request(self, request):
|
344
|
+
print("Processing request")
|
345
|
+
|
346
|
+
def process_response(self, request, response):
|
347
|
+
print("Processing response")
|
348
|
+
|
349
|
+
app.add_middleware(SimpleMiddleware)
|
350
|
+
```
|
186
351
|
"""
|
187
352
|
self.middleware.add(middleware_cls)
|
188
353
|
|
189
354
|
def test_session(self, base_url="http://testserver"):
|
355
|
+
"""
|
356
|
+
Create a test client session for this application.
|
357
|
+
|
358
|
+
This provides an interface similar to the requests library for testing
|
359
|
+
your application without making actual HTTP calls.
|
360
|
+
|
361
|
+
Args:
|
362
|
+
base_url: Base URL to use for requests (default: "http://testserver")
|
363
|
+
|
364
|
+
Returns:
|
365
|
+
A requests.Session object configured to call this application
|
366
|
+
|
367
|
+
Example:
|
368
|
+
```python
|
369
|
+
client = app.test_session()
|
370
|
+
response = client.get("/home")
|
371
|
+
assert response.status_code == 200
|
372
|
+
```
|
373
|
+
"""
|
190
374
|
session = RequestsSession()
|
191
375
|
session.mount(prefix=base_url, adapter=RequestsWSGIAdapter(self))
|
192
376
|
return session
|
plinx/methods.py
CHANGED
@@ -2,7 +2,37 @@ from enum import Enum
|
|
2
2
|
|
3
3
|
|
4
4
|
class HTTPMethods(Enum):
|
5
|
-
"""
|
5
|
+
"""
|
6
|
+
Enumeration of HTTP methods supported by the Plinx framework.
|
7
|
+
|
8
|
+
This enum defines the standard HTTP methods as defined in RFC 7231 and RFC 5789,
|
9
|
+
categorized by their safety and idempotency properties.
|
10
|
+
|
11
|
+
Safe methods (should not modify resources):
|
12
|
+
- GET: Retrieve a representation of a resource
|
13
|
+
- HEAD: Same as GET but returns only headers, no body
|
14
|
+
|
15
|
+
Idempotent methods (multiple identical requests have same effect as single request):
|
16
|
+
- PUT: Replace a resource with the request payload
|
17
|
+
- DELETE: Remove the specified resource
|
18
|
+
- OPTIONS: Describe the communication options for the target resource
|
19
|
+
|
20
|
+
Non-idempotent methods (multiple identical requests may have different effects):
|
21
|
+
- POST: Submit data to be processed, typically creating a new resource
|
22
|
+
- PATCH: Apply partial modifications to a resource
|
23
|
+
|
24
|
+
Usage:
|
25
|
+
```python
|
26
|
+
from plinx.methods import HTTPMethods
|
27
|
+
|
28
|
+
# Check if a method is GET
|
29
|
+
if method == HTTPMethods.GET:
|
30
|
+
# handle GET request
|
31
|
+
|
32
|
+
# Get the string value of a method
|
33
|
+
method_str = HTTPMethods.POST.value # "POST"
|
34
|
+
```
|
35
|
+
"""
|
6
36
|
|
7
37
|
# Safe methods
|
8
38
|
GET = "GET"
|
@@ -15,4 +45,33 @@ class HTTPMethods(Enum):
|
|
15
45
|
|
16
46
|
# Non-idempotent methods
|
17
47
|
POST = "POST"
|
18
|
-
PATCH = "PATCH"
|
48
|
+
PATCH = "PATCH"
|
49
|
+
|
50
|
+
|
51
|
+
def is_valid_method(method: str) -> bool:
|
52
|
+
"""
|
53
|
+
Check if the given method is a valid HTTP method.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
method: The HTTP method to check
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
bool: True if the method is valid, False otherwise
|
60
|
+
"""
|
61
|
+
return method in HTTPMethods.__members__.values()
|
62
|
+
|
63
|
+
|
64
|
+
def get_handler_name_for_method(method: str) -> str:
|
65
|
+
"""
|
66
|
+
Get the handler name for a given HTTP method.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
method: The HTTP method to get the handler name for
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
str: The handler name corresponding to the HTTP method
|
73
|
+
"""
|
74
|
+
if not is_valid_method(method):
|
75
|
+
raise ValueError(f"Invalid HTTP method: {method}")
|
76
|
+
|
77
|
+
return method.lower()
|
plinx/middleware.py
CHANGED
@@ -3,10 +3,51 @@ from webob import Request, Response
|
|
3
3
|
|
4
4
|
class Middleware:
|
5
5
|
"""
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
Base class for all Plinx middleware components.
|
7
|
+
|
8
|
+
The middleware system in Plinx follows a nested pattern where each middleware
|
9
|
+
wraps the application or another middleware component. This allows for a chain
|
10
|
+
of processing both before a request reaches the application and after the
|
11
|
+
application generates a response.
|
12
|
+
|
13
|
+
Middleware classes should inherit from this base class and override the
|
14
|
+
`process_request` and `process_response` methods to implement custom behavior.
|
15
|
+
|
16
|
+
The middleware execution flow works like this:
|
17
|
+
1. Client request comes in
|
18
|
+
2. Each middleware's `process_request` is called from outermost to innermost
|
19
|
+
3. The application handles the request
|
20
|
+
4. Each middleware's `process_response` is called from innermost to outermost
|
21
|
+
5. The response is sent back to the client
|
22
|
+
|
23
|
+
Examples:
|
24
|
+
```python
|
25
|
+
class LoggingMiddleware(Middleware):
|
26
|
+
def process_request(self, request):
|
27
|
+
print(f"Request: {request.path}")
|
28
|
+
|
29
|
+
def process_response(self, request, response):
|
30
|
+
print(f"Response: {response.status_code}")
|
31
|
+
|
32
|
+
app = Plinx()
|
33
|
+
app.add_middleware(LoggingMiddleware)
|
34
|
+
```
|
35
|
+
|
36
|
+
Middleware that modifies the request or response:
|
37
|
+
|
38
|
+
```python
|
39
|
+
class AuthMiddleware(Middleware):
|
40
|
+
def process_request(self, request):
|
41
|
+
request.user = None
|
42
|
+
auth_header = request.headers.get("Authorization", "")
|
43
|
+
if auth_header.startswith("Bearer "):
|
44
|
+
token = auth_header[7:]
|
45
|
+
request.user = self.get_user_from_token(token)
|
46
|
+
|
47
|
+
def get_user_from_token(self, token):
|
48
|
+
# Implementation to validate token and return user
|
49
|
+
pass
|
50
|
+
```
|
10
51
|
"""
|
11
52
|
|
12
53
|
def __init__(
|
@@ -14,8 +55,10 @@ class Middleware:
|
|
14
55
|
app,
|
15
56
|
):
|
16
57
|
"""
|
17
|
-
|
18
|
-
|
58
|
+
Initialize the middleware with a WSGI application.
|
59
|
+
|
60
|
+
Args:
|
61
|
+
app: A WSGI application (typically a Plinx instance or another middleware)
|
19
62
|
"""
|
20
63
|
self.app = app
|
21
64
|
|
@@ -25,10 +68,19 @@ class Middleware:
|
|
25
68
|
start_response: callable,
|
26
69
|
):
|
27
70
|
"""
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
71
|
+
WSGI callable interface for the middleware.
|
72
|
+
|
73
|
+
This method makes middleware instances callable according to the WSGI spec,
|
74
|
+
allowing them to be used in a WSGI server. It creates a Request object,
|
75
|
+
passes it to the application's handle_request method, and returns the
|
76
|
+
response.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
environ: The WSGI environment dictionary
|
80
|
+
start_response: The WSGI start_response callable
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
An iterable of bytes representing the response body
|
32
84
|
"""
|
33
85
|
request = Request(environ)
|
34
86
|
|
@@ -41,8 +93,14 @@ class Middleware:
|
|
41
93
|
middleware_cls,
|
42
94
|
):
|
43
95
|
"""
|
44
|
-
Add a middleware
|
45
|
-
|
96
|
+
Add a new middleware to the stack.
|
97
|
+
|
98
|
+
This method creates an instance of the provided middleware class,
|
99
|
+
passing the current middleware instance (or application) as the app parameter.
|
100
|
+
This builds up a chain of nested middleware instances.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
middleware_cls: A class inheriting from Middleware
|
46
104
|
"""
|
47
105
|
self.app = middleware_cls(self.app)
|
48
106
|
|
@@ -51,10 +109,15 @@ class Middleware:
|
|
51
109
|
request: Request,
|
52
110
|
):
|
53
111
|
"""
|
54
|
-
Process the request before it
|
55
|
-
|
112
|
+
Process the request before it reaches the application.
|
113
|
+
|
114
|
+
Override this method in your middleware subclass to modify or inspect
|
115
|
+
the request before it's handled by the application or the next middleware.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
request: The WebOb Request object
|
56
119
|
"""
|
57
|
-
pass
|
120
|
+
pass # pragma: no cover
|
58
121
|
|
59
122
|
def process_response(
|
60
123
|
self,
|
@@ -62,17 +125,31 @@ class Middleware:
|
|
62
125
|
response: Response,
|
63
126
|
):
|
64
127
|
"""
|
65
|
-
Process the response after it
|
66
|
-
|
67
|
-
|
128
|
+
Process the response after it's generated by the application.
|
129
|
+
|
130
|
+
Override this method in your middleware subclass to modify or inspect
|
131
|
+
the response before it's returned to the client or the previous middleware.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
request: The WebOb Request object that generated this response
|
135
|
+
response: The Response object to be returned
|
68
136
|
"""
|
69
|
-
pass
|
137
|
+
pass # pragma: no cover
|
70
138
|
|
71
139
|
def handle_request(self, request: Request):
|
72
140
|
"""
|
73
|
-
|
74
|
-
|
75
|
-
|
141
|
+
Process a request through this middleware and the wrapped application.
|
142
|
+
|
143
|
+
This method implements the middleware chain by:
|
144
|
+
1. Calling this middleware's process_request method
|
145
|
+
2. Passing the request to the wrapped application/middleware
|
146
|
+
3. Calling this middleware's process_response method with the response
|
147
|
+
|
148
|
+
Args:
|
149
|
+
request: The WebOb Request object
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
The Response object after processing
|
76
153
|
"""
|
77
154
|
self.process_request(request)
|
78
155
|
response = self.app.handle_request(request)
|
plinx/orm/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
from .orm import Column, Database, ForeignKey, Table # noqa
|