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 CHANGED
@@ -1 +1,3 @@
1
1
  from .applications import Plinx
2
+
3
+ __version__ = "1.0.1"
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
- Entrypoint for the WSGI application.
36
- :param environ: The WSGI environment.
37
- :param start_response: The WSGI callable.
38
- :return: The response body produced by the middleware.
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
- Add a route to the application. Django-like syntax.
50
- :param path: The path to register.
51
- :param handler: The handler to register.
52
- :return:
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 with the given path.
65
- :param path: The path to register.
66
- :return:
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
- """Allow access to HTTP method decorators like app.get, app.post etc."""
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
- Creates a decorator for registering routes with a specific HTTP method.
89
- :param method: The HTTP method enum value
90
- :return: Decorator function
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
- Handle the given request and return the response.
108
- :param request: The request object.
109
- :return: The response object.
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[Callable, dict | None] | Tuple[None, None]:
279
+ ) -> Tuple[Tuple[HTTPMethods, Callable] | None, dict | None]:
155
280
  """
156
- Find the handler for the given request.
157
- If no handler is found, set the response status code to 404
158
- and return None.
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
- :param request: The request object.
161
- :param response: The response object.
162
- :return: A tuple containing the handler and the named parameters.
163
- If no handler is found, return tuple[None, None].
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
- :param middleware_cls: The middleware class to add.
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
- """HTTP methods for the API."""
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
- Middleware base class for all middleware classes.
7
- This class is used to create middleware for the Plinx application.
8
- Middleware classes should inherit from this class and implement the
9
- `process_request` and `process_response` methods.
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
- Middleware base class for all middleware classes.
18
- :param app: The WSGI application.
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
- Entrypoint for the WSGI middleware since it is now entrypoint for the WSGI application.
29
- :param environ: The WSGI environment.
30
- :param start_response: The WSGI callable.
31
- :return: The response body.
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 class to the application.
45
- :param middleware_cls: The middleware class to add.
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 is passed to the application.
55
- :param request: The request object.
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 # pragma: no cover
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 is passed to the application.
66
- :param request: The request object.
67
- :param response: The response object.
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 # pragma: no cover
137
+ pass # pragma: no cover
70
138
 
71
139
  def handle_request(self, request: Request):
72
140
  """
73
- Handle the incoming request.
74
- :param request: The request object.
75
- :return: The response object.
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