Plinx 1.0.0__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/orm.py +324 -14
- plinx/orm/utils.py +2 -2
- plinx/response.py +78 -6
- plinx/status_codes.py +26 -5
- plinx/utils.py +18 -3
- {Plinx-1.0.0.dist-info → plinx-1.0.1.dist-info}/METADATA +16 -3
- plinx-1.0.1.dist-info/RECORD +15 -0
- {Plinx-1.0.0.dist-info → plinx-1.0.1.dist-info}/WHEEL +1 -1
- Plinx-1.0.0.dist-info/RECORD +0 -15
- {Plinx-1.0.0.dist-info → plinx-1.0.1.dist-info/licenses}/LICENSE +0 -0
- {Plinx-1.0.0.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/orm.py
CHANGED
@@ -8,19 +8,121 @@ T = TypeVar("T")
|
|
8
8
|
|
9
9
|
|
10
10
|
class Database:
|
11
|
+
"""
|
12
|
+
SQLite database wrapper that provides a simple ORM interface.
|
13
|
+
|
14
|
+
The Database class is the main entry point for ORM operations in Plinx.
|
15
|
+
It handles database connections, table creation, and provides methods for
|
16
|
+
basic CRUD operations (Create, Read, Update, Delete) on Table objects.
|
17
|
+
|
18
|
+
This class uses SQLite as the underlying database engine and provides
|
19
|
+
a simplified interface that avoids writing raw SQL in most cases.
|
20
|
+
|
21
|
+
Examples:
|
22
|
+
Creating a database and defining models:
|
23
|
+
|
24
|
+
```python
|
25
|
+
from plinx.orm import Database, Table, Column, ForeignKey
|
26
|
+
|
27
|
+
db = Database("app.db")
|
28
|
+
|
29
|
+
class User(Table):
|
30
|
+
name = Column(str)
|
31
|
+
age = Column(int)
|
32
|
+
|
33
|
+
db.create(User)
|
34
|
+
|
35
|
+
# Create a new user
|
36
|
+
john = User(name="John Doe", age=30)
|
37
|
+
db.save(john)
|
38
|
+
|
39
|
+
# Query users
|
40
|
+
all_users = db.all(User)
|
41
|
+
john = db.get(User, id=1)
|
42
|
+
|
43
|
+
# Update a user
|
44
|
+
john.age = 31
|
45
|
+
db.update(john)
|
46
|
+
|
47
|
+
# Delete a user
|
48
|
+
db.delete(john)
|
49
|
+
```
|
50
|
+
"""
|
51
|
+
|
11
52
|
def __init__(self, path: str):
|
53
|
+
"""
|
54
|
+
Initialize a new database connection.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
path: Path to the SQLite database file. If the file doesn't exist,
|
58
|
+
it will be created.
|
59
|
+
"""
|
12
60
|
self.connection = sqlite3.Connection(path)
|
13
61
|
|
14
62
|
def create(self, table: "Table"):
|
63
|
+
"""
|
64
|
+
Create a database table based on a Table subclass definition.
|
65
|
+
|
66
|
+
This method creates a table in the database with columns corresponding
|
67
|
+
to the Column and ForeignKey attributes defined on the Table subclass.
|
68
|
+
If the table already exists, this method does nothing.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
table: A Table subclass with Column and/or ForeignKey attributes
|
72
|
+
|
73
|
+
Example:
|
74
|
+
```python
|
75
|
+
class User(Table):
|
76
|
+
name = Column(str)
|
77
|
+
age = Column(int)
|
78
|
+
|
79
|
+
db.create(User)
|
80
|
+
```
|
81
|
+
"""
|
15
82
|
self.connection.execute(table._get_create_sql())
|
16
83
|
|
17
84
|
def save(self, instance: "Table"):
|
85
|
+
"""
|
86
|
+
Save a Table instance to the database.
|
87
|
+
|
88
|
+
This method inserts a new row into the corresponding database table.
|
89
|
+
It automatically sets the instance's id attribute to the new row's ID.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
instance: A Table instance to save
|
93
|
+
|
94
|
+
Example:
|
95
|
+
```python
|
96
|
+
user = User(name="John Doe", age=30)
|
97
|
+
db.save(user) # user.id is now set to the new row's ID
|
98
|
+
```
|
99
|
+
"""
|
18
100
|
sql, values = instance._get_insert_sql()
|
19
101
|
cursor = self.connection.execute(sql, values)
|
20
102
|
instance._data["id"] = cursor.lastrowid
|
21
103
|
self.connection.commit()
|
22
104
|
|
23
105
|
def all(self, table: "Table"):
|
106
|
+
"""
|
107
|
+
Retrieve all rows from a table.
|
108
|
+
|
109
|
+
This method selects all rows from the table corresponding to the given
|
110
|
+
Table subclass. It returns a list of instances of that class, with
|
111
|
+
attributes set to the values from the database.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
table: A Table subclass to query
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
List of Table instances, one for each row in the table
|
118
|
+
|
119
|
+
Example:
|
120
|
+
```python
|
121
|
+
all_users = db.all(User)
|
122
|
+
for user in all_users:
|
123
|
+
print(f"{user.name} is {user.age} years old")
|
124
|
+
```
|
125
|
+
"""
|
24
126
|
sql, fields = table._get_select_all_sql()
|
25
127
|
rows = self.connection.execute(sql).fetchall()
|
26
128
|
|
@@ -40,6 +142,32 @@ class Database:
|
|
40
142
|
return result
|
41
143
|
|
42
144
|
def get(self, table: "Table", **kwargs):
|
145
|
+
"""
|
146
|
+
Retrieve a single row from a table by specified criteria.
|
147
|
+
|
148
|
+
This method selects a row from the database where the specified columns
|
149
|
+
match the given values. It returns an instance of the Table subclass with
|
150
|
+
attributes set to the values from the database.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
table: A Table subclass to query
|
154
|
+
**kwargs: Column-value pairs to filter by
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
A Table instance corresponding to the matched row
|
158
|
+
|
159
|
+
Raises:
|
160
|
+
Exception: If no row matches the criteria
|
161
|
+
|
162
|
+
Example:
|
163
|
+
```python
|
164
|
+
# Get user by ID
|
165
|
+
user = db.get(User, id=1)
|
166
|
+
|
167
|
+
# Get user by name
|
168
|
+
user = db.get(User, name="John Doe")
|
169
|
+
```
|
170
|
+
"""
|
43
171
|
sql, fields, params = table._get_select_where_sql(**kwargs)
|
44
172
|
row = self.connection.execute(sql, params).fetchone()
|
45
173
|
|
@@ -59,42 +187,164 @@ class Database:
|
|
59
187
|
return table(**properties)
|
60
188
|
|
61
189
|
def update(self, instance: "Table"):
|
190
|
+
"""
|
191
|
+
Update an existing row in the database.
|
192
|
+
|
193
|
+
This method updates the row corresponding to the given instance with the
|
194
|
+
current values of the instance's attributes.
|
195
|
+
|
196
|
+
Args:
|
197
|
+
instance: A Table instance to update. Must have an id attribute.
|
198
|
+
|
199
|
+
Example:
|
200
|
+
```python
|
201
|
+
user = db.get(User, id=1)
|
202
|
+
user.name = "Jane Doe"
|
203
|
+
db.update(user)
|
204
|
+
```
|
205
|
+
"""
|
62
206
|
sql, values = instance._get_update_sql()
|
63
207
|
self.connection.execute(sql, values)
|
64
208
|
self.connection.commit()
|
65
209
|
|
66
210
|
def delete(self, instance: "Table"):
|
211
|
+
"""
|
212
|
+
Delete a row from the database.
|
213
|
+
|
214
|
+
This method deletes the row corresponding to the given instance.
|
215
|
+
|
216
|
+
Args:
|
217
|
+
instance: A Table instance to delete. Must have an id attribute.
|
218
|
+
|
219
|
+
Example:
|
220
|
+
```python
|
221
|
+
user = db.get(User, id=1)
|
222
|
+
db.delete(user)
|
223
|
+
```
|
224
|
+
"""
|
67
225
|
sql, values = instance._get_delete_sql()
|
68
226
|
self.connection.execute(sql, values)
|
69
227
|
self.connection.commit()
|
70
228
|
|
71
229
|
def close(self):
|
230
|
+
"""
|
231
|
+
Close the database connection.
|
232
|
+
|
233
|
+
This method closes the SQLite connection when the database is no longer
|
234
|
+
needed. It's good practice to call this method when you're done using
|
235
|
+
the database, especially in longer-running applications.
|
236
|
+
"""
|
72
237
|
if self.connection:
|
73
238
|
self.connection.close()
|
74
239
|
self.connection = None
|
75
240
|
|
76
241
|
@property
|
77
242
|
def tables(self):
|
243
|
+
"""
|
244
|
+
Get a list of all tables in the database.
|
245
|
+
|
246
|
+
Returns:
|
247
|
+
List of table names as strings
|
248
|
+
"""
|
78
249
|
SELECT_TABLES_SQL = "SELECT name FROM sqlite_master WHERE type = 'table';"
|
79
250
|
return [x[0] for x in self.connection.execute(SELECT_TABLES_SQL).fetchall()]
|
80
251
|
|
81
252
|
|
82
253
|
class Column:
|
254
|
+
"""
|
255
|
+
Define a column in a database table.
|
256
|
+
|
257
|
+
This class represents a column definition for a Table class. It stores
|
258
|
+
the column's type and can generate the corresponding SQL type.
|
259
|
+
|
260
|
+
Examples:
|
261
|
+
```python
|
262
|
+
class User(Table):
|
263
|
+
name = Column(str) # TEXT column
|
264
|
+
age = Column(int) # INTEGER column
|
265
|
+
active = Column(bool) # INTEGER column (0=False, 1=True)
|
266
|
+
```
|
267
|
+
"""
|
268
|
+
|
83
269
|
def __init__(self, type: Generic[T]):
|
270
|
+
"""
|
271
|
+
Initialize a new column.
|
272
|
+
|
273
|
+
Args:
|
274
|
+
type: Python type for the column (str, int, float, bool, bytes)
|
275
|
+
"""
|
84
276
|
self.type = type
|
85
277
|
|
86
278
|
@property
|
87
279
|
def sql_type(self):
|
280
|
+
"""
|
281
|
+
Get the SQL type corresponding to this column's Python type.
|
282
|
+
|
283
|
+
Returns:
|
284
|
+
SQL type string (e.g., "TEXT", "INTEGER", "REAL")
|
285
|
+
"""
|
88
286
|
return SQLITE_TYPE_MAP[self.type]
|
89
287
|
|
90
288
|
|
91
289
|
class ForeignKey:
|
290
|
+
"""
|
291
|
+
Define a foreign key relationship between tables.
|
292
|
+
|
293
|
+
This class represents a foreign key constraint in a database schema,
|
294
|
+
linking one Table class to another.
|
295
|
+
|
296
|
+
Examples:
|
297
|
+
```python
|
298
|
+
class Author(Table):
|
299
|
+
name = Column(str)
|
300
|
+
|
301
|
+
class Book(Table):
|
302
|
+
title = Column(str)
|
303
|
+
author = ForeignKey(Author) # Creates author_id column
|
304
|
+
```
|
305
|
+
"""
|
306
|
+
|
92
307
|
def __init__(self, table):
|
308
|
+
"""
|
309
|
+
Initialize a new foreign key.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
table: The Table subclass that this foreign key references
|
313
|
+
"""
|
93
314
|
self.table = table
|
94
315
|
|
95
316
|
|
96
317
|
class Table:
|
318
|
+
"""
|
319
|
+
Base class for ORM models in Plinx.
|
320
|
+
|
321
|
+
This class is used as a base class for defining database tables.
|
322
|
+
Subclasses should define class attributes using Column and ForeignKey
|
323
|
+
to describe the table schema.
|
324
|
+
|
325
|
+
The Table class provides methods for generating SQL statements for
|
326
|
+
CRUD operations, which are used by the Database class.
|
327
|
+
|
328
|
+
Examples:
|
329
|
+
```python
|
330
|
+
class User(Table):
|
331
|
+
name = Column(str)
|
332
|
+
age = Column(int)
|
333
|
+
|
334
|
+
class Post(Table):
|
335
|
+
title = Column(str)
|
336
|
+
content = Column(str)
|
337
|
+
author = ForeignKey(User)
|
338
|
+
```
|
339
|
+
"""
|
340
|
+
|
97
341
|
def __init__(self, **kwargs):
|
342
|
+
"""
|
343
|
+
Initialize a new record.
|
344
|
+
|
345
|
+
Args:
|
346
|
+
**kwargs: Column values to initialize with
|
347
|
+
"""
|
98
348
|
self._data = {"id": None}
|
99
349
|
|
100
350
|
for key, value in kwargs.items():
|
@@ -102,8 +352,16 @@ class Table:
|
|
102
352
|
|
103
353
|
def __getattribute__(self, key):
|
104
354
|
"""
|
105
|
-
|
106
|
-
|
355
|
+
Custom attribute access for Table instances.
|
356
|
+
|
357
|
+
This method allows Table instances to access column values as attributes,
|
358
|
+
rather than accessing self._data directly.
|
359
|
+
|
360
|
+
Args:
|
361
|
+
key: Attribute name to access
|
362
|
+
|
363
|
+
Returns:
|
364
|
+
The attribute value
|
107
365
|
"""
|
108
366
|
# Why use super().__getattribute__ instead of self._data[key]?
|
109
367
|
# Because otherwise it will create an infinite loop since __getattribute__ will call itself
|
@@ -115,7 +373,14 @@ class Table:
|
|
115
373
|
|
116
374
|
def __setattr__(self, key, value):
|
117
375
|
"""
|
118
|
-
|
376
|
+
Custom attribute assignment for Table instances.
|
377
|
+
|
378
|
+
This method ensures that when setting an attribute that corresponds to
|
379
|
+
a column, the value is stored in self._data.
|
380
|
+
|
381
|
+
Args:
|
382
|
+
key: Attribute name to set
|
383
|
+
value: Value to assign
|
119
384
|
"""
|
120
385
|
super().__setattr__(key, value)
|
121
386
|
if key in self._data:
|
@@ -123,6 +388,12 @@ class Table:
|
|
123
388
|
|
124
389
|
@classmethod
|
125
390
|
def _get_create_sql(cls):
|
391
|
+
"""
|
392
|
+
Generate SQL for creating the table.
|
393
|
+
|
394
|
+
Returns:
|
395
|
+
SQL string for creating the table
|
396
|
+
"""
|
126
397
|
CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS {name} ({fields});"
|
127
398
|
fields = [
|
128
399
|
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
@@ -139,6 +410,12 @@ class Table:
|
|
139
410
|
return CREATE_TABLE_SQL.format(name=name, fields=fields)
|
140
411
|
|
141
412
|
def _get_insert_sql(self):
|
413
|
+
"""
|
414
|
+
Generate SQL for inserting a record.
|
415
|
+
|
416
|
+
Returns:
|
417
|
+
Tuple of (SQL string, parameter values list)
|
418
|
+
"""
|
142
419
|
INSERT_SQL = "INSERT INTO {name} ({fields}) VALUES ({placeholders});"
|
143
420
|
|
144
421
|
cls = self.__class__
|
@@ -167,6 +444,12 @@ class Table:
|
|
167
444
|
|
168
445
|
@classmethod
|
169
446
|
def _get_select_all_sql(cls):
|
447
|
+
"""
|
448
|
+
Generate SQL for selecting all records.
|
449
|
+
|
450
|
+
Returns:
|
451
|
+
Tuple of (SQL string, field names list)
|
452
|
+
"""
|
170
453
|
SELECT_ALL_SQL = "SELECT {fields} FROM {name};"
|
171
454
|
|
172
455
|
fields = ["id"]
|
@@ -177,13 +460,25 @@ class Table:
|
|
177
460
|
elif isinstance(field, ForeignKey):
|
178
461
|
fields.append(name + "_id")
|
179
462
|
|
180
|
-
return
|
181
|
-
|
182
|
-
|
183
|
-
|
463
|
+
return (
|
464
|
+
SELECT_ALL_SQL.format(
|
465
|
+
fields=", ".join(fields),
|
466
|
+
name=cls.__name__.lower(),
|
467
|
+
),
|
468
|
+
fields,
|
469
|
+
)
|
184
470
|
|
185
471
|
@classmethod
|
186
472
|
def _get_select_where_sql(cls, **kwargs):
|
473
|
+
"""
|
474
|
+
Generate SQL for selecting records by criteria.
|
475
|
+
|
476
|
+
Args:
|
477
|
+
**kwargs: Column-value pairs to filter by
|
478
|
+
|
479
|
+
Returns:
|
480
|
+
Tuple of (SQL string, field names list, parameter values list)
|
481
|
+
"""
|
187
482
|
SELECT_WHERE_SQL = "SELECT {fields} FROM {name} WHERE {query};"
|
188
483
|
|
189
484
|
fields = ["id"]
|
@@ -211,6 +506,12 @@ class Table:
|
|
211
506
|
)
|
212
507
|
|
213
508
|
def _get_update_sql(self):
|
509
|
+
"""
|
510
|
+
Generate SQL for updating a record.
|
511
|
+
|
512
|
+
Returns:
|
513
|
+
Tuple of (SQL string, parameter values list)
|
514
|
+
"""
|
214
515
|
UPDATE_SQL = "UPDATE {name} SET {fields} WHERE id = ?;"
|
215
516
|
|
216
517
|
cls = self.__class__
|
@@ -227,14 +528,23 @@ class Table:
|
|
227
528
|
|
228
529
|
values.append(getattr(self, "id"))
|
229
530
|
|
230
|
-
return
|
231
|
-
|
232
|
-
|
233
|
-
|
531
|
+
return (
|
532
|
+
UPDATE_SQL.format(
|
533
|
+
name=cls.__name__.lower(),
|
534
|
+
fields=", ".join([f"{field} = ?" for field in fields]),
|
535
|
+
),
|
536
|
+
values,
|
537
|
+
)
|
234
538
|
|
235
539
|
def _get_delete_sql(self):
|
540
|
+
"""
|
541
|
+
Generate SQL for deleting a record.
|
542
|
+
|
543
|
+
Returns:
|
544
|
+
Tuple of (SQL string, parameter values list)
|
545
|
+
"""
|
236
546
|
DELETE_SQL = "DELETE FROM {name} WHERE id = ?;"
|
237
547
|
|
238
|
-
return DELETE_SQL.format(
|
239
|
-
|
240
|
-
|
548
|
+
return DELETE_SQL.format(name=self.__class__.__name__.lower()), [
|
549
|
+
getattr(self, "id")
|
550
|
+
]
|
plinx/orm/utils.py
CHANGED
plinx/response.py
CHANGED
@@ -6,7 +6,57 @@ from webob import Response as WebObResponse
|
|
6
6
|
|
7
7
|
|
8
8
|
class PlinxResponse:
|
9
|
+
"""
|
10
|
+
Response class for the Plinx web framework.
|
11
|
+
|
12
|
+
This class provides a simple interface for constructing HTTP responses,
|
13
|
+
with high-level helpers for common response types like JSON and plain text.
|
14
|
+
It wraps WebOb's Response for actual WSGI compliance and output generation.
|
15
|
+
|
16
|
+
The class provides multiple ways to set response content:
|
17
|
+
|
18
|
+
1. Set the `text` attribute for plain text responses
|
19
|
+
2. Set the `json` attribute for JSON responses
|
20
|
+
3. Set the `body` attribute directly for binary data
|
21
|
+
|
22
|
+
It also allows setting status codes, content types, and custom headers.
|
23
|
+
|
24
|
+
Examples:
|
25
|
+
Plain text response:
|
26
|
+
```python
|
27
|
+
def handler(request, response):
|
28
|
+
response.text = "Hello, World!"
|
29
|
+
response.status_code = 200 # Optional, defaults to 200
|
30
|
+
```
|
31
|
+
|
32
|
+
JSON response:
|
33
|
+
```python
|
34
|
+
def handler(request, response):
|
35
|
+
response.json = {"message": "Hello, World!"}
|
36
|
+
# Content-Type will automatically be set to application/json
|
37
|
+
```
|
38
|
+
|
39
|
+
Custom headers:
|
40
|
+
```python
|
41
|
+
def handler(request, response):
|
42
|
+
response.text = "Not Found"
|
43
|
+
response.status_code = 404
|
44
|
+
response.headers["X-Custom-Header"] = "Value"
|
45
|
+
```
|
46
|
+
"""
|
47
|
+
|
9
48
|
def __init__(self):
|
49
|
+
"""
|
50
|
+
Initialize a new response object.
|
51
|
+
|
52
|
+
Sets up default values for the response attributes:
|
53
|
+
- json: None (will be serialized to JSON if set)
|
54
|
+
- text: None (will be encoded to UTF-8 if set)
|
55
|
+
- content_type: None (will be set based on response type)
|
56
|
+
- body: Empty bytes (raw response body)
|
57
|
+
- status_code: 200 (OK)
|
58
|
+
- headers: Empty dict (custom HTTP headers)
|
59
|
+
"""
|
10
60
|
self.json = None
|
11
61
|
self.text = None
|
12
62
|
self.content_type = None
|
@@ -20,10 +70,18 @@ class PlinxResponse:
|
|
20
70
|
start_response: StartResponse,
|
21
71
|
) -> Iterable[bytes]:
|
22
72
|
"""
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
73
|
+
WSGI callable interface for the response.
|
74
|
+
|
75
|
+
This makes the response object act as a WSGI application,
|
76
|
+
which is required for compatibility with WSGI servers.
|
77
|
+
It delegates the actual WSGI handling to WebOb's Response.
|
78
|
+
|
79
|
+
Args:
|
80
|
+
environ: The WSGI environment dictionary
|
81
|
+
start_response: The WSGI start_response callable
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
An iterable of bytes representing the response body
|
27
85
|
"""
|
28
86
|
|
29
87
|
self.set_body_and_content_type()
|
@@ -37,12 +95,26 @@ class PlinxResponse:
|
|
37
95
|
return response(environ, start_response)
|
38
96
|
|
39
97
|
def set_body_and_content_type(self):
|
98
|
+
"""
|
99
|
+
Prepare the response body and content type based on the response attributes.
|
100
|
+
|
101
|
+
This method is called automatically before the response is returned.
|
102
|
+
It handles the conversion of high-level response attributes (`json`, `text`)
|
103
|
+
into the raw response body and appropriate content type.
|
104
|
+
|
105
|
+
The priority order is:
|
106
|
+
1. If `json` is set, encode it as JSON and set content_type to application/json
|
107
|
+
2. If `text` is set, encode it as UTF-8 and set content_type to text/plain
|
108
|
+
3. Otherwise, use the existing `body` and `content_type`
|
109
|
+
"""
|
40
110
|
if self.json is not None:
|
41
111
|
self.body = json.dumps(self.json).encode("UTF-8")
|
42
112
|
self.content_type = "application/json"
|
43
113
|
elif self.text is not None:
|
44
|
-
self.body =
|
114
|
+
self.body = (
|
115
|
+
self.text.encode("utf-8") if isinstance(self.text, str) else self.text
|
116
|
+
)
|
45
117
|
self.content_type = "text/plain"
|
46
118
|
|
47
119
|
if self.content_type is not None:
|
48
|
-
self.headers["Content-Type"] = self.content_type
|
120
|
+
self.headers["Content-Type"] = self.content_type
|
plinx/status_codes.py
CHANGED
@@ -2,18 +2,39 @@ from enum import Enum
|
|
2
2
|
|
3
3
|
|
4
4
|
class StatusCodes(Enum):
|
5
|
-
"""
|
5
|
+
"""
|
6
|
+
Enumeration of HTTP status codes supported by the Plinx framework.
|
6
7
|
|
7
|
-
|
8
|
+
This enum defines standard HTTP status codes as defined in RFC 7231,
|
9
|
+
organized by their categories (2xx for success, 3xx for redirection, etc.).
|
10
|
+
|
11
|
+
Each status code has a name that reflects its standard description,
|
12
|
+
and a numeric value that is sent in HTTP responses.
|
13
|
+
|
14
|
+
Usage:
|
15
|
+
```python
|
16
|
+
from plinx.status_codes import StatusCodes
|
17
|
+
|
18
|
+
# Set a 404 status code
|
19
|
+
response.status_code = StatusCodes.NOT_FOUND.value # 404
|
20
|
+
|
21
|
+
# Get the name of a status code for display
|
22
|
+
status_name = StatusCodes.NOT_FOUND.name # "NOT_FOUND"
|
23
|
+
# Can be formatted for human display:
|
24
|
+
human_readable = StatusCodes.NOT_FOUND.name.replace("_", " ").title() # "Not Found"
|
25
|
+
```
|
26
|
+
"""
|
27
|
+
|
28
|
+
# 2XX - Success
|
8
29
|
OK = 200
|
9
30
|
CREATED = 201
|
10
31
|
ACCEPTED = 202
|
11
32
|
NO_CONTENT = 204
|
12
33
|
|
13
|
-
# 3XX
|
34
|
+
# 3XX - Redirection
|
14
35
|
NOT_MODIFIED = 304
|
15
36
|
|
16
|
-
# 4XX
|
37
|
+
# 4XX - Client Error
|
17
38
|
BAD_REQUEST = 400
|
18
39
|
UNAUTHORIZED = 401
|
19
40
|
FORBIDDEN = 403
|
@@ -27,7 +48,7 @@ class StatusCodes(Enum):
|
|
27
48
|
TOO_MANY_REQUESTS = 429
|
28
49
|
UNAVAILABLE_FOR_LEGAL_REASONS = 451
|
29
50
|
|
30
|
-
# 5XX
|
51
|
+
# 5XX - Server Error
|
31
52
|
INTERNAL_SERVER_ERROR = 500
|
32
53
|
NOT_IMPLEMENTED = 501
|
33
54
|
BAD_GATEWAY = 502
|
plinx/utils.py
CHANGED
@@ -5,9 +5,24 @@ from plinx.status_codes import StatusCodes
|
|
5
5
|
|
6
6
|
def handle_404(response: Response) -> None:
|
7
7
|
"""
|
8
|
-
Set
|
9
|
-
|
10
|
-
|
8
|
+
Set up a standardized 404 Not Found response.
|
9
|
+
|
10
|
+
This utility function is used internally by the framework when no route
|
11
|
+
matches the requested URL path. It sets the status code to 404 and
|
12
|
+
the response text to "Not Found".
|
13
|
+
|
14
|
+
Args:
|
15
|
+
response: The Response object to configure with 404 status
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
None
|
19
|
+
|
20
|
+
Example:
|
21
|
+
```python
|
22
|
+
response = Response()
|
23
|
+
handle_404(response)
|
24
|
+
# response now has status_code=404 and text="Not Found"
|
25
|
+
```
|
11
26
|
"""
|
12
27
|
response.status_code = StatusCodes.NOT_FOUND.value
|
13
28
|
response.text = StatusCodes.NOT_FOUND.name.replace("_", " ").title()
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: Plinx
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.1
|
4
4
|
Summary: Plinx is an experimental, minimalistic, and extensible web framework and ORM written in Python.
|
5
5
|
Home-page: https://github.com/dhavalsavalia/plinx
|
6
6
|
Author: Dhaval Savalia
|
@@ -17,6 +17,19 @@ Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
18
18
|
Requires-Dist: webob
|
19
19
|
Requires-Dist: parse
|
20
|
+
Requires-Dist: requests
|
21
|
+
Requires-Dist: requests-wsgi-adapter
|
22
|
+
Dynamic: author
|
23
|
+
Dynamic: author-email
|
24
|
+
Dynamic: classifier
|
25
|
+
Dynamic: description
|
26
|
+
Dynamic: description-content-type
|
27
|
+
Dynamic: home-page
|
28
|
+
Dynamic: license
|
29
|
+
Dynamic: license-file
|
30
|
+
Dynamic: requires-dist
|
31
|
+
Dynamic: requires-python
|
32
|
+
Dynamic: summary
|
20
33
|
|
21
34
|
|
22
35
|
# Plinx
|
@@ -68,7 +81,7 @@ app = Plinx()
|
|
68
81
|
|
69
82
|
@app.route("/")
|
70
83
|
def index(request, response):
|
71
|
-
response.text = "Hello, Plinx 1.0.
|
84
|
+
response.text = "Hello, Plinx 1.0.1!"
|
72
85
|
|
73
86
|
# Example using the ORM (requires database setup)
|
74
87
|
# from plinx.orm import Database, Table, Column
|
@@ -0,0 +1,15 @@
|
|
1
|
+
plinx/__init__.py,sha256=TkcBtaKtAwXDE6XKgnkfC-fIzNxtCErHDt0WCh1ZF74,54
|
2
|
+
plinx/applications.py,sha256=XM5jPYgeJapYxaGcTzphcpr_281et0QDwPMfAGIbRtA,11483
|
3
|
+
plinx/methods.py,sha256=uLZmj3KQ4QF-JVNgq3fVY7kOJkKJDMQghyiogU8m2Yc,2101
|
4
|
+
plinx/middleware.py,sha256=Kdeau4eZ5f4nKTlH_CNvaIndYwkgIdtWBP50-khEK5s,5050
|
5
|
+
plinx/response.py,sha256=-CgkW4cH9pLSa73YdrnceCInObwPzpSplhnWfAg-360,4079
|
6
|
+
plinx/status_codes.py,sha256=dPkHkcnULbhrxk6WYhR5wmrd2sKhOQf409G_OtX8Cug,1536
|
7
|
+
plinx/utils.py,sha256=da0uiLEvLMrjROeUJ5lfBTRAIqKoSdKkqImPs1qHsF4,769
|
8
|
+
plinx/orm/__init__.py,sha256=ryDdzvF6Eh6RIHyEgym6UKCaFMnMitwucazpiWuvICQ,61
|
9
|
+
plinx/orm/orm.py,sha256=-eHAgURYWw5kL9zMCO1Mha5gRcPCbqoAY6BALj-gqJc,15790
|
10
|
+
plinx/orm/utils.py,sha256=fkP89dYMcNFUOcRryxGTNUEV0Dr40-qwG-IH2AxY4DQ,118
|
11
|
+
plinx-1.0.1.dist-info/licenses/LICENSE,sha256=MljMjTJD6oOY41eZWLDZ4x8FrTUcl7ElKxWIXJXUwLM,1066
|
12
|
+
plinx-1.0.1.dist-info/METADATA,sha256=nvJIwZYncIm-dAB8_oZC8FX4W8Ow2yr3UA_xBd1WPEg,3116
|
13
|
+
plinx-1.0.1.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
14
|
+
plinx-1.0.1.dist-info/top_level.txt,sha256=U_4P3aFsTEhhvfiE3sVbJKAG9Fp4IPC5d6zpttayAZw,6
|
15
|
+
plinx-1.0.1.dist-info/RECORD,,
|
Plinx-1.0.0.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
plinx/__init__.py,sha256=oMho3a3JJZllxtj4lLwS96pwWRBvctDJRFww5lfqlBs,32
|
2
|
-
plinx/applications.py,sha256=Oy2pO1zyfKWyYXeJ8AuzdoAab6XJThNHDbOdpKWA5-8,5957
|
3
|
-
plinx/methods.py,sha256=CEaousqNXbgdyaEL0BdOtAWh1p03jAaAFzXJkY9xII0,294
|
4
|
-
plinx/middleware.py,sha256=OiNDhKWpmpKkAIbSWxRzP_tQXSyfDToos-MQMDPSUWM,2161
|
5
|
-
plinx/response.py,sha256=oDZPF9V76HhqdUZLzSmoLw_q-GG9hc5krfCBgNm3OfU,1460
|
6
|
-
plinx/status_codes.py,sha256=V7vX7_ujYIaVjO3I3eVUnuNKiTXMSov56wJt8tvMbAM,700
|
7
|
-
plinx/utils.py,sha256=WaU_j7YXsdlZh3M33B2vXjRPAU26lBeQ5sV2ZasXVwE,352
|
8
|
-
plinx/orm/__init__.py,sha256=ryDdzvF6Eh6RIHyEgym6UKCaFMnMitwucazpiWuvICQ,61
|
9
|
-
plinx/orm/orm.py,sha256=FdwLdT-4I5rnhfTCu7v9iBZZ5xlZHZ36equqPAHYvhk,7335
|
10
|
-
plinx/orm/utils.py,sha256=uXT2JV9cVgLTnm_EkguqJglsbIfSPXcnjMQBNFJ8M08,116
|
11
|
-
Plinx-1.0.0.dist-info/LICENSE,sha256=MljMjTJD6oOY41eZWLDZ4x8FrTUcl7ElKxWIXJXUwLM,1066
|
12
|
-
Plinx-1.0.0.dist-info/METADATA,sha256=cDxPb9HkViq-kG_JWVI3YFWI6APojOUiPSZSotzBfFY,2819
|
13
|
-
Plinx-1.0.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
14
|
-
Plinx-1.0.0.dist-info/top_level.txt,sha256=U_4P3aFsTEhhvfiE3sVbJKAG9Fp4IPC5d6zpttayAZw,6
|
15
|
-
Plinx-1.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|