py-flexo 0.1.0__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.
py_flexo/__init__.py ADDED
File without changes
py_flexo/app.py ADDED
@@ -0,0 +1,111 @@
1
+ from webob import Request
2
+ from .response import Response
3
+ from parse import parse
4
+ import requests
5
+ import wsgiadapter
6
+ import inspect
7
+ from jinja2 import Environment, FileSystemLoader
8
+ import os
9
+ from whitenoise import WhiteNoise
10
+ from .middleware import Middleware
11
+
12
+
13
+ class PyFlexoApp:
14
+ def __init__(self, templates_dir="templates", static_dir = "static"):
15
+ self._routes = {}
16
+ self.template_env = Environment(
17
+ loader = FileSystemLoader(os.path.abspath(templates_dir))
18
+ )
19
+ self.exception_handler = None
20
+
21
+ self.whitenoise = WhiteNoise(self.wsgi_app, root=static_dir, prefix='/static')
22
+ self.middleware = Middleware(self)
23
+
24
+ def __call__(self, environ, start_response):
25
+ path_info = environ["PATH_INFO"]
26
+ if path_info.startswith('/static'):
27
+ return self.whitenoise(environ, start_response)
28
+ return self.middleware(environ, start_response)
29
+
30
+ def wsgi_app(self, environ, start_response):
31
+ request = Request(environ)
32
+ response = self.handle_request(request)
33
+ return response(environ, start_response)
34
+
35
+ def handle_request(self, request):
36
+ response = Response()
37
+
38
+ handler_data, kwargs = self.find_handler(request.path)
39
+
40
+ if handler_data:
41
+ handler = handler_data["handler"]
42
+ allow_methods = handler_data["allow_methods"]
43
+ if inspect.isclass(handler):
44
+ handler = getattr(handler(), request.method.lower(), None)
45
+ if handler:
46
+ handler(request, response, **kwargs)
47
+ else:
48
+ self.method_not_allowed_response(response)
49
+ else:
50
+ if request.method.lower() not in allow_methods:
51
+ self.method_not_allowed_response(response)
52
+ try:
53
+ handler(request, response, **kwargs)
54
+ except Exception as e:
55
+ if self.exception_handler is not None:
56
+ self.exception_handler(request, response, e)
57
+ else:
58
+ raise e
59
+ else:
60
+ self.default_response(response)
61
+ return response
62
+
63
+ def find_handler(self, path):
64
+ for route, handler_data in self._routes.items():
65
+ result = parse(route, path)
66
+ if result:
67
+ return handler_data, result.named
68
+ if route == path:
69
+ return handler_data, None
70
+ return None, None
71
+
72
+ def default_response(self, response):
73
+ response.status_code = 404
74
+ response.text = 'Not Found'
75
+ return response
76
+
77
+ def method_not_allowed_response(self, res):
78
+ res.status_code = 405
79
+ res.text = 'Method Not Allowed'
80
+ return res
81
+
82
+ def add_route(self, path, handler, allow_methods = None):
83
+ assert path not in self._routes, "Duplicate url. Please change the URL"
84
+
85
+ if allow_methods is None:
86
+ allow_methods = ["get", "post", "put", "path", "head", "options", "delete"]
87
+
88
+ self._routes[path] = {"handler":handler, "allow_methods": allow_methods}
89
+
90
+ def route(self, path, allow_methods = None):
91
+ def decorator(handler):
92
+ self.add_route(path, handler, allow_methods)
93
+ return handler
94
+ return decorator
95
+
96
+
97
+ def test_session(self):
98
+ session = requests.Session()
99
+ session.mount('http://testserver', wsgiadapter.WSGIAdapter(self))
100
+ return session
101
+
102
+ def template(self, template_name, context = None):
103
+ if context is None:
104
+ context = {}
105
+ return self.template_env.get_template(template_name).render(**context).encode()
106
+
107
+ def add_exception_handler(self, handler):
108
+ self.exception_handler = handler
109
+
110
+ def add_middleware(self, middleware_cls):
111
+ self.middleware.add(middleware_cls)
py_flexo/middleware.py ADDED
@@ -0,0 +1,26 @@
1
+ from webob import Request
2
+
3
+ class Middleware:
4
+ def __init__(self, app):
5
+ self.app = app
6
+
7
+ def add(self, middleware_cls):
8
+ self.app = middleware_cls(self.app)
9
+
10
+ def process_request(self, req):
11
+ pass
12
+
13
+ def process_response(self, resp):
14
+ pass
15
+
16
+ def handle_request(self, request):
17
+ self.process_request(request)
18
+ response = self.app.handle_request(request)
19
+ self.process_response(response)
20
+
21
+ return response
22
+
23
+ def __call__(self, environ, start_response):
24
+ request = Request(environ)
25
+ response = self.app.handle_request(request)
26
+ return response(environ, start_response)
py_flexo/response.py ADDED
@@ -0,0 +1,35 @@
1
+ from webob import Response as WebResponse
2
+ import json
3
+
4
+ class Response:
5
+ def __init__(self):
6
+ self.json = None
7
+ self.html = None
8
+ self.text = None
9
+ self.content_type = None
10
+ self.body = b''
11
+ self.status_code = 200
12
+
13
+ def set_body_and_content_type(self):
14
+ if self.json:
15
+ self.body = json.dumps(self.json).encode()
16
+ self.content_type = "application/json"
17
+
18
+ if self.html:
19
+ self.body = self.html.encode()
20
+ self.content_type = "text/html"
21
+
22
+ if self.text:
23
+ self.body = self.text
24
+ self.content_type = "text/plain"
25
+
26
+
27
+ def __call__(self, environ, start_response):
28
+ self.set_body_and_content_type()
29
+ response = WebResponse(
30
+ body = self.body,
31
+ content_type=self.content_type,
32
+ status = self.status_code
33
+ )
34
+
35
+ return response(environ, start_response)
@@ -0,0 +1,391 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-flexo
3
+ Version: 0.1.0
4
+ Summary: Python Web Framework built for learning purposes
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: gunicorn>=26.0.0
8
+ Requires-Dist: jinja2>=3.1.6
9
+ Requires-Dist: parse>=1.22.1
10
+ Requires-Dist: requests-wsgi-adapter>=0.4.1
11
+ Requires-Dist: webob>=1.8.10
12
+ Requires-Dist: whitenoise>=6.12.0
13
+
14
+ # PyFlexo
15
+
16
+ <p align="center">
17
+ A modern, lightweight and extensible WSGI web framework for Python.
18
+ </p>
19
+
20
+ <p align="center">
21
+
22
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue)
23
+ ![License](https://img.shields.io/badge/license-MIT-green)
24
+ ![Tests](https://img.shields.io/badge/tests-passing-success)
25
+ ![Version](https://img.shields.io/badge/version-0.1.0-orange)
26
+
27
+ </p>
28
+
29
+ ---
30
+
31
+ ## Why PyFlexo?
32
+
33
+ PyFlexo is a lightweight WSGI web framework focused on simplicity, readability, and extensibility.
34
+
35
+ Instead of hiding how web applications work, PyFlexo provides a clean API while remaining close to the WSGI specification. It is suitable for learning, small services, REST APIs, and custom web applications.
36
+
37
+ ## Features
38
+
39
+ * Simple and intuitive routing
40
+ * Function-based views
41
+ * Class-based views
42
+ * Dynamic URL parameters
43
+ * Built-in template rendering (Jinja2)
44
+ * Static file serving
45
+ * JSON response helper
46
+ * Global exception handlers
47
+ * Middleware support
48
+ * Pure Python implementation
49
+ * WSGI compatible
50
+ * Easy to extend
51
+
52
+ ---
53
+
54
+ # Installation
55
+
56
+ Using pip
57
+
58
+ ```bash
59
+ pip install py_flexo
60
+ ```
61
+
62
+ Using uv
63
+
64
+ ```bash
65
+ uv add py_flexo
66
+ ```
67
+
68
+ ---
69
+
70
+ # Quick Start
71
+ main.py
72
+ ```python
73
+ from py_flexo.app import PyFlexoApp
74
+
75
+ app = PyFlexoApp()
76
+
77
+
78
+ @app.route("/")
79
+ def home(request, response):
80
+ response.text = "Hello, PyFlexo!"
81
+ ```
82
+
83
+ Run your application with any WSGI server.
84
+
85
+ Example:
86
+
87
+ ```bash
88
+ gunicorn main:app
89
+ ```
90
+
91
+ ---
92
+
93
+ # Routing
94
+
95
+ ## Function Based View
96
+
97
+ ```python
98
+ @app.route("/")
99
+ def index(request, response):
100
+ response.text = "Hello World"
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Route Parameters
106
+
107
+ ```python
108
+ @app.route("/hello/{name}")
109
+ def hello(request, response, name):
110
+ response.text = f"Hello {name}"
111
+ ```
112
+
113
+ Example
114
+
115
+ ```
116
+ GET /hello/Alice
117
+ ```
118
+
119
+ Response
120
+
121
+ ```
122
+ Hello Alice
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Class Based Views
128
+
129
+ ```python
130
+ @app.route("/books")
131
+ class Book:
132
+
133
+ def get(self, request, response):
134
+ response.text = "Book List"
135
+
136
+ def post(self, request, response):
137
+ response.text = "Create Book"
138
+ ```
139
+
140
+ Supported methods
141
+
142
+ * GET
143
+ * POST
144
+ * PUT
145
+ * PATCH
146
+ * DELETE
147
+
148
+ ---
149
+
150
+ # Templates
151
+
152
+ Directory
153
+
154
+ ```
155
+ project/
156
+
157
+ templates/
158
+ home.html
159
+ ```
160
+
161
+ Route
162
+
163
+ ```python
164
+ @app.route("/home")
165
+ def home(request, response):
166
+ response.html = app.template(
167
+ "home.html",
168
+ context={
169
+ "title": "PyFlexo",
170
+ "header": "Welcome"
171
+ }
172
+ )
173
+ ```
174
+
175
+ Template
176
+
177
+ ```html
178
+ <!DOCTYPE html>
179
+ <html>
180
+ <head>
181
+ <title>{{ title }}</title>
182
+ </head>
183
+
184
+ <body>
185
+
186
+ <h1>{{ header }}</h1>
187
+
188
+ </body>
189
+ </html>
190
+ ```
191
+
192
+ ---
193
+
194
+ # JSON Responses
195
+
196
+ ```python
197
+ @app.route("/api")
198
+ def api(request, response):
199
+
200
+ response.json = {
201
+ "framework": "PyFlexo",
202
+ "version": "0.1.0"
203
+ }
204
+ ```
205
+
206
+ Output
207
+
208
+ ```json
209
+ {
210
+ "framework": "PyFlexo",
211
+ "version": "0.1.0"
212
+ }
213
+ ```
214
+
215
+ ---
216
+
217
+ # Middleware
218
+
219
+ ```python
220
+ from py_flexo.middleware import Middleware
221
+
222
+
223
+ class LoggingMiddleware(Middleware):
224
+
225
+ def process_request(self, request):
226
+ print(request.method, request.path)
227
+
228
+ def process_response(self, response):
229
+ print(response.status_code)
230
+
231
+
232
+ app.add_middleware(LoggingMiddleware)
233
+ ```
234
+
235
+ ---
236
+
237
+ # Exception Handling
238
+
239
+ ```python
240
+ def exception_handler(request, response, exception):
241
+
242
+ response.status_code = 500
243
+ response.text = "Internal Server Error"
244
+
245
+
246
+ app.add_exception_handler(exception_handler)
247
+ ```
248
+
249
+ ---
250
+
251
+ # Static Files
252
+
253
+ Directory
254
+
255
+ ```
256
+ project/
257
+
258
+ static/
259
+
260
+ style.css
261
+ app.js
262
+ logo.png
263
+ ```
264
+
265
+ Static files are automatically available under
266
+
267
+ ```
268
+ /static/
269
+ ```
270
+
271
+ Example
272
+
273
+ ```
274
+ /static/style.css
275
+ ```
276
+
277
+ ---
278
+
279
+ # Project Structure
280
+
281
+ ```
282
+ project/
283
+
284
+
285
+
286
+ ├── main.py
287
+
288
+ ├── templates/
289
+
290
+ │ └── home.html
291
+
292
+
293
+
294
+ ├── static/
295
+
296
+ │ ├── style.css
297
+
298
+ │ └── app.js
299
+
300
+
301
+
302
+ └── pyproject.toml
303
+ ```
304
+
305
+ ---
306
+
307
+ # Complete Example
308
+
309
+ ```python
310
+ from py_flexo.app import PyFlexoApp
311
+ from py_flexo.middleware import Middleware
312
+
313
+ app = PyFlexoApp()
314
+
315
+
316
+ @app.route("/")
317
+ def index(request, response):
318
+ response.text = "Welcome to PyFlexo"
319
+
320
+
321
+ @app.route("/hello/{name}")
322
+ def hello(request, response, name):
323
+ response.text = f"Hello {name}"
324
+
325
+
326
+ @app.route("/books")
327
+ class Book:
328
+
329
+ def get(self, request, response):
330
+ response.text = "Book List"
331
+
332
+
333
+ @app.route("/home")
334
+ def home(request, response):
335
+ response.html = app.template(
336
+ "home.html",
337
+ context={
338
+ "title": "PyFlexo",
339
+ "header": "Welcome"
340
+ }
341
+ )
342
+
343
+
344
+ @app.route("/api")
345
+ def api(request, response):
346
+ response.json = {
347
+ "message": "Hello JSON"
348
+ }
349
+
350
+
351
+ class LoggingMiddleware(Middleware):
352
+
353
+ def process_request(self, request):
354
+ print(request.path)
355
+
356
+ def process_response(self, response):
357
+ print(response.status_code)
358
+
359
+
360
+ app.add_middleware(LoggingMiddleware)
361
+ ```
362
+
363
+ ---
364
+
365
+ # Roadmap
366
+
367
+ * Cookies
368
+ * Sessions
369
+ * Authentication
370
+ * Blueprint support
371
+ * Request validation
372
+ * CLI
373
+ * Dependency Injection
374
+ * OpenAPI
375
+ * ASGI support
376
+ * WebSocket support
377
+
378
+ ---
379
+
380
+
381
+ # Contributing
382
+
383
+ Contributions are welcome.
384
+
385
+ Please open an issue before submitting large changes.
386
+
387
+ ---
388
+
389
+ # Author
390
+
391
+ PyFlexo is an open-source project created for developers who enjoy building fast and elegant Python web applications.
@@ -0,0 +1,8 @@
1
+ py_flexo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ py_flexo/app.py,sha256=cXxYfCaolNvdcPRXGCc7ETX5zK-_M_OsVNoR047-aKs,3813
3
+ py_flexo/middleware.py,sha256=RXxZ-nEpbpT0gOSecdiU0MXZN-WjIvXst5DTmRgfOO8,660
4
+ py_flexo/response.py,sha256=3IyacG-Re60hlPUl_P5AETAOMUrxy4-_Kg5O8CVZRbk,936
5
+ py_flexo-0.1.0.dist-info/METADATA,sha256=W8j-4ux9n1DrKaKajdWfbeU3-psidSIwBSGoxrqzcAk,5444
6
+ py_flexo-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ py_flexo-0.1.0.dist-info/top_level.txt,sha256=3aF-mNjvgm11XVnHqNN1j-b_bqxWT-mG0017FKtKSZc,9
8
+ py_flexo-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ py_flexo