serpy-rest 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.
serpy/__init__.py ADDED
@@ -0,0 +1,190 @@
1
+ import json
2
+ from urllib.parse import parse_qs
3
+
4
+ class Request:
5
+ def __init__(self, scope, receive):
6
+ self.scope = scope
7
+ self._query_params = None
8
+ self.receive = receive
9
+ self._headers = None
10
+
11
+ async def json(self):
12
+ body = b''
13
+ more_body = True
14
+ while more_body:
15
+ message = await self.receive()
16
+ body += message.get('body', b'')
17
+ more_body = message.get('more_body', False)
18
+
19
+ if not body:
20
+ return None
21
+ return json.loads(body)
22
+
23
+ @property
24
+ def path(self):
25
+ return self.scope['path']
26
+
27
+
28
+ @property
29
+ def method(self):
30
+ return self.scope['method']
31
+
32
+
33
+ @property
34
+ def headers(self):
35
+ if self._headers is None:
36
+ self._headers = {
37
+ key.decode('utf-8'): value.decode('utf-8')
38
+ for key, value in self.scope['headers']
39
+ }
40
+ return self._headers
41
+
42
+
43
+ @property
44
+ def query(self):
45
+ if self._query_params is None:
46
+ query_string = self.scope['query_string'].decode("utf-8")
47
+ self._query_params = QueryParams(query_string)
48
+ return self._query_params
49
+
50
+
51
+
52
+ class QueryParams:
53
+ def __init__(self, query_string):
54
+ self._params = parse_qs(query_string)
55
+
56
+ def get(self, key, default=None):
57
+ values = self._params.get(key, [])
58
+ return values[0] if values else default
59
+
60
+ def getall(self, key, default=None):
61
+ return self._params.get(key, default or [])
62
+
63
+
64
+
65
+ class Response:
66
+ def __init__(self, content, status_code=200):
67
+ self.content = content
68
+ self.status_code = status_code
69
+ self.headers = {}
70
+
71
+ async def send_to_asgi(self, send):
72
+ body = json.dumps(self.content).encode("utf-8")
73
+
74
+ headers_list = [[b"content-type", b"application/json"]]
75
+ for key, value in self.headers.items():
76
+ headers_list.append([key.encode("utf-8"), value.encode("utf-8")])
77
+
78
+ await send({
79
+ "type": "http.response.start",
80
+ "status": self.status_code,
81
+ "headers": headers_list,
82
+ })
83
+ await send({"type": "http.response.body", "body": body})
84
+
85
+
86
+
87
+ class Router:
88
+ def __init__(self):
89
+ self.routes = {}
90
+
91
+ def route(self, path, methods=None):
92
+ if methods is None: methods = ["GET"]
93
+ def wrapper(handler):
94
+ for method in methods:
95
+ self.routes[(method.upper(), path)] = handler
96
+ return handler
97
+ return wrapper
98
+
99
+
100
+
101
+ class MiddlewareWrapper:
102
+ def __init__(self, app, middleware_func):
103
+ self.app = app
104
+ self.middleware_func = middleware_func
105
+
106
+ async def __call__(self, scope, receive):
107
+ async def call_next(request):
108
+ response = await self.app(scope, receive)
109
+ return response
110
+
111
+ request = Request(scope, receive)
112
+ response = await self.middleware_func(request, call_next)
113
+ return response
114
+
115
+
116
+
117
+ class SerPy:
118
+ def __init__(self):
119
+ self.routes = {}
120
+ self.app = self._dispatch_request
121
+
122
+ def add_middleware(self, middleware_func):
123
+ self.app = MiddlewareWrapper(self.app, middleware_func)
124
+
125
+ def include_router(self, router: Router, prefix=""):
126
+ for (method, path), handler in router.routes.items():
127
+ self.routes[(method, prefix + path)] = handler
128
+
129
+ def route(self, path, methods=None, middleware=None):
130
+ if methods is None: methods = ["GET"]
131
+ if middleware is None: middleware = []
132
+
133
+ def wrapper(handler):
134
+ for method in methods:
135
+ self.routes[(method.upper(), path)] = (handler, middleware)
136
+ return handler
137
+ return wrapper
138
+
139
+ def find_route(self, method, path):
140
+ for (route_method, route_path), (handler, middleware) in self.routes.items():
141
+
142
+ if route_method != method: continue
143
+
144
+ route_parts = route_path.split("/")
145
+ path_parts = path.split("/")
146
+
147
+ if len(route_parts) != len(path_parts): continue
148
+
149
+ params = {}
150
+ match = True
151
+
152
+ for route_part, path_part in zip(route_parts, path_parts):
153
+ if route_part.startswith('{') and route_part.endswith('}'):
154
+ param_name = route_part[1:-1]
155
+ params[param_name] = path_part
156
+ elif route_part != path_part:
157
+ match = False
158
+ break
159
+ if match:
160
+ return handler, params, middleware
161
+ return None, None, []
162
+
163
+ async def _dispatch_request(self, scope, receive):
164
+
165
+ request = Request(scope, receive)
166
+ handler, params, route_middleware = self.find_route(request.method, request.path)
167
+
168
+ if not handler:
169
+ return Response(content={"error": "Route Not Found"}, status_code=404)
170
+
171
+ async def handler_call(req):
172
+ return await handler(req, **params)
173
+
174
+ chain = handler_call
175
+ for mw in reversed(route_middleware):
176
+ chain = self._wrap_middleware_for_dispatch(mw, chain)
177
+
178
+ response = await chain(request)
179
+ return response
180
+
181
+ def _wrap_middleware_for_dispatch(self, middleware_func, next_in_chain):
182
+ async def new_chain_link(request):
183
+ return await middleware_func(request, next_in_chain)
184
+ return new_chain_link
185
+
186
+
187
+ async def __call__(self, scope, receive, send):
188
+ if scope["type"] != "http": return
189
+ response = await self.app(scope, receive)
190
+ await response.send_to_asgi(send)
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: serpy-rest
3
+ Version: 0.1.0
4
+ Summary: A minimal ASGI web framework for Python.
5
+ Home-page: https://github.com/ragav2005/SerPy
6
+ Author: Ragav Vignes
7
+ Author-email: ragavvignesviswanathan@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Framework :: AsyncIO
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: classifier
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
23
+ # SerPy
24
+
25
+ SerPy is a minimal, modern, and lightning-fast ASGI web framework for Python. It is designed for simplicity, flexibility, and performance, making it ideal for building APIs, microservices, and web applications with minimal overhead.
26
+
27
+ ---
28
+
29
+ ## Table of Contents
30
+
31
+ - [Features](#features)
32
+ - [Installation](#installation)
33
+ - [Quick Start](#quick-start)
34
+ - [Example Project](#example-project)
35
+ - [Middleware](#middleware)
36
+ - [Advanced Routing](#advanced-routing)
37
+ - [Contributing](#contributing)
38
+ - [Running Locally](#running-locally)
39
+ - [License](#license)
40
+ - [Contact](#contact)
41
+
42
+ ---
43
+
44
+ ## Features
45
+
46
+ - **ASGI compatible**: Works seamlessly with ASGI servers like Uvicorn and Hypercorn
47
+ - **Simple routing**: Path parameters, method-based routing, and easy route registration
48
+ - **Middleware support**: Add custom middleware for logging, authentication, CORS, etc.
49
+ - **Request/Response abstraction**: Clean API for handling requests and responses
50
+ - **Modular**: Use routers to organize your application
51
+ - **Type hints**: Modern Pythonic code with type hints
52
+
53
+ ---
54
+
55
+ ## Installation
56
+
57
+ Install the latest release from PyPI:
58
+
59
+ ```bash
60
+ pip install serpy-asgi
61
+ ```
62
+
63
+ Or, to use the latest development version, clone this repository:
64
+
65
+ ```bash
66
+ git clone https://github.com/ragav2005/SerPy.git
67
+ cd SerPy
68
+ pip install .
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Quick Start
74
+
75
+ Create a file called `main.py`:
76
+
77
+ ```python
78
+ from serpy import SerPy, Response
79
+
80
+ app = SerPy()
81
+
82
+ @app.route("/hello/{name}", methods=["GET"])
83
+ async def hello(request, name):
84
+ return Response({"message": f"Hello, {name}!"})
85
+
86
+ # Run with: uvicorn main:app
87
+ ```
88
+
89
+ Start the server:
90
+
91
+ ```bash
92
+ uvicorn main:app --reload
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Example Project Structure
98
+
99
+ ```
100
+ myproject/
101
+ ├── main.py
102
+ ├── requirements.txt
103
+ └── ...
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Middleware
109
+
110
+ Add global middleware to your app:
111
+
112
+ ```python
113
+ async def log_middleware(request, call_next):
114
+ print(f"Request: {request.method} {request.path}")
115
+ response = await call_next(request)
116
+ return response
117
+
118
+ app.add_middleware(log_middleware)
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Advanced Routing
124
+
125
+ Use routers to organize endpoints:
126
+
127
+ ```python
128
+ from serpy import Router
129
+
130
+ user_router = Router()
131
+
132
+ @user_router.route("/users/{user_id}", methods=["GET"])
133
+ async def get_user(request, user_id):
134
+ # Fetch user logic here
135
+ return Response({"user_id": user_id})
136
+
137
+ app.include_router(user_router, prefix="/api")
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Contributing
143
+
144
+ Contributions are welcome! To contribute:
145
+
146
+ 1. Fork the repository
147
+ 2. Create a new branch (`git checkout -b feature/your-feature`)
148
+ 3. Commit your changes
149
+ 4. Push to your fork and submit a pull request
150
+
151
+ Please ensure your code follows PEP8 and includes tests where appropriate.
152
+
153
+ ---
154
+
155
+ ## Running Locally (Development)
156
+
157
+ 1. Clone the repository:
158
+ ```bash
159
+ git clone https://github.com/ragav2005/SerPy.git
160
+ cd SerPy
161
+ ```
162
+ 2. (Optional) Create a virtual environment:
163
+ ```bash
164
+ python3 -m venv .venv
165
+ source .venv/bin/activate
166
+ ```
167
+ 3. Install dependencies:
168
+ ```bash
169
+ pip install -r requirements.txt
170
+ ```
171
+ 4. Run the example app or your own app with Uvicorn:
172
+ ```bash
173
+ uvicorn main:app --reload
174
+ ```
175
+
176
+ ---
177
+
178
+ ## License
179
+
180
+ This project is licensed under the MIT License.
181
+
182
+ ---
183
+
184
+ ## Contact
185
+
186
+ Author: [ragav2005](https://github.com/ragav2005)
187
+ For questions, suggestions, or issues, please open an issue on GitHub.
@@ -0,0 +1,5 @@
1
+ serpy/__init__.py,sha256=Wl08BQXRIsHSGJ85OqC05o2UqRdmItRBtiKbXHj-rUg,5616
2
+ serpy_rest-0.1.0.dist-info/METADATA,sha256=Eb3r-BDvv9Mf3MYB_8QQn1cnzTbRkWs_j0uKlSghDDo,3918
3
+ serpy_rest-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
4
+ serpy_rest-0.1.0.dist-info/top_level.txt,sha256=taqeUQycKG1R6VbUW9mD8vWeuHPE6UouxvPjgquT3tk,6
5
+ serpy_rest-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ serpy