bamboo-framework 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.
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: bamboo-framework
3
+ Version: 0.1.0
4
+ Summary: A lightweight, readable Python API framework. Simple by design.
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/myaxiomai/bambooapi
7
+ Project-URL: Repository, https://github.com/myaxiomai/bambooapi
8
+ Keywords: api,framework,asgi,web,bamboo
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Internet :: WWW/HTTP
17
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Bamboo
22
+
23
+ A lightweight, readable Python API framework. Simple by design.
24
+
25
+ Bamboo is an ASGI micro-framework you can read in one sitting and fully understand.
26
+ No magic. No hidden complexity. Just clean, honest Python.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install bambooapi
32
+ ```
33
+
34
+ ## Quickstart
35
+
36
+ ```python
37
+ from bambooapi import Bamboo, Response
38
+
39
+ app = Bamboo()
40
+
41
+ @app.get("/")
42
+ async def home(request):
43
+ """Welcome message."""
44
+ return {"message": "Hello from Bamboo"}
45
+
46
+ @app.post("/notes")
47
+ async def create_note(request):
48
+ """Create a note."""
49
+ data = await request.json()
50
+ return Response({"note": data}, status=201)
51
+ ```
52
+
53
+ Run it:
54
+
55
+ ```bash
56
+ uvicorn myapp:app
57
+ ```
58
+
59
+ Then visit `http://localhost:8000/docs` for the interactive API docs.
60
+
61
+ ## Features
62
+
63
+ - GET, POST, PUT, DELETE routing
64
+ - Path parameters: `/notes/{note_id}`
65
+ - Async request body and JSON parsing
66
+ - Auto-generated OpenAPI spec at `/openapi.json`
67
+ - Interactive docs page at `/docs`
68
+ - Zero dependencies beyond an ASGI server
69
+
70
+ ## Philosophy
71
+
72
+ The entire framework fits in one file you can read in an afternoon.
73
+ If you can read it, you can understand it. If you can understand it, you can trust it.
74
+
75
+ ## License
76
+
77
+ MIT
@@ -0,0 +1,5 @@
1
+ bambooapi/__init__.py,sha256=A2IC6g3a9RzQEBFGIkorhUhh9Z8isIVFEgdzdrCJSrs,6215
2
+ bamboo_framework-0.1.0.dist-info/METADATA,sha256=aRUcABtaIRhJJpKOhyFIx4DkLaCB0vEHzuJZlfL5Fg0,2007
3
+ bamboo_framework-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
4
+ bamboo_framework-0.1.0.dist-info/top_level.txt,sha256=OC2Ykq0zH09-CCQQ5ZCLGM41mPiAXAL-Bg2k-3ghCJ8,10
5
+ bamboo_framework-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
+ bambooapi
bambooapi/__init__.py ADDED
@@ -0,0 +1,197 @@
1
+ import json
2
+ import re
3
+
4
+
5
+ class Response:
6
+ def __init__(self, body, status=200, headers=None):
7
+ self.body = body
8
+ self.status = status
9
+ self.headers = headers or []
10
+
11
+
12
+ class Request:
13
+ def __init__(self, scope, receive):
14
+ self.scope = scope
15
+ self.receive = receive
16
+ self.method = scope["method"]
17
+ self.path = scope["path"]
18
+
19
+ async def body(self):
20
+ chunks = []
21
+ more = True
22
+ while more:
23
+ event = await self.receive()
24
+ chunks.append(event.get("body", b""))
25
+ more = event.get("more_body", False)
26
+ return b"".join(chunks)
27
+
28
+ async def json(self):
29
+ raw = await self.body()
30
+ if not raw:
31
+ return None
32
+ return json.loads(raw)
33
+
34
+
35
+ SWAGGER_UI_HTML = """<!DOCTYPE html>
36
+ <html lang="en">
37
+ <head>
38
+ <meta charset="utf-8" />
39
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
40
+ <title>{title} - API docs</title>
41
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" />
42
+ </head>
43
+ <body>
44
+ <div id="swagger-ui"></div>
45
+ <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
46
+ <script>
47
+ window.onload = function () {{
48
+ window.ui = SwaggerUIBundle({{
49
+ url: "/openapi.json",
50
+ dom_id: "#swagger-ui"
51
+ }});
52
+ }};
53
+ </script>
54
+ </body>
55
+ </html>
56
+ """
57
+
58
+
59
+ class Bamboo:
60
+ def __init__(self, title="Bamboo", version="0.1.0"):
61
+ self.title = title
62
+ self.version = version
63
+ self.routes = []
64
+
65
+ def route(self, method, path):
66
+ pattern = self.compile_path(path)
67
+ def decorator(func):
68
+ self.routes.append((method.upper(), path, pattern, func))
69
+ return func
70
+ return decorator
71
+
72
+ def get(self, path):
73
+ return self.route("GET", path)
74
+
75
+ def post(self, path):
76
+ return self.route("POST", path)
77
+
78
+ def put(self, path):
79
+ return self.route("PUT", path)
80
+
81
+ def delete(self, path):
82
+ return self.route("DELETE", path)
83
+
84
+ def compile_path(self, path):
85
+ segments = [s for s in path.split("/") if s != ""]
86
+ regex_parts = []
87
+ for segment in segments:
88
+ if segment.startswith("{") and segment.endswith("}"):
89
+ name = segment[1:-1]
90
+ regex_parts.append(r"(?P<%s>[^/]+)" % name)
91
+ else:
92
+ regex_parts.append(re.escape(segment))
93
+ if regex_parts:
94
+ regex = "^/" + "/".join(regex_parts) + "/?$"
95
+ else:
96
+ regex = "^/?$"
97
+ return re.compile(regex)
98
+
99
+ def openapi(self):
100
+ paths = {}
101
+ for method, path, pattern, handler in self.routes:
102
+ param_names = re.findall(r"{([^}]+)}", path)
103
+ parameters = [
104
+ {
105
+ "name": name,
106
+ "in": "path",
107
+ "required": True,
108
+ "schema": {"type": "string"},
109
+ }
110
+ for name in param_names
111
+ ]
112
+ doc = (handler.__doc__ or "").strip()
113
+ summary = doc.splitlines()[0] if doc else handler.__name__
114
+ operation = {
115
+ "summary": summary,
116
+ "responses": {"200": {"description": "Successful Response"}},
117
+ }
118
+ if parameters:
119
+ operation["parameters"] = parameters
120
+ paths.setdefault(path, {})[method.lower()] = operation
121
+ return {
122
+ "openapi": "3.0.0",
123
+ "info": {"title": self.title, "version": self.version},
124
+ "paths": paths,
125
+ }
126
+
127
+ async def __call__(self, scope, receive, send):
128
+ if scope["type"] != "http":
129
+ return
130
+
131
+ request = Request(scope, receive)
132
+ method = request.method
133
+ path = request.path
134
+
135
+ if method == "GET" and path == "/openapi.json":
136
+ await self.send_json(send, self.openapi())
137
+ return
138
+
139
+ if method == "GET" and path == "/docs":
140
+ await self.send_html(send, SWAGGER_UI_HTML.format(title=self.title))
141
+ return
142
+
143
+ for route_method, route_path, pattern, handler in self.routes:
144
+ if route_method != method:
145
+ continue
146
+ match = pattern.match(path)
147
+ if match:
148
+ params = match.groupdict()
149
+ try:
150
+ result = await handler(request, **params)
151
+ if isinstance(result, Response):
152
+ await self.send_response(send, result)
153
+ else:
154
+ await self.send_json(send, result)
155
+ except Exception:
156
+ await self.send_json(send, {"error": "internal server error"}, 500)
157
+ return
158
+
159
+ await self.send_json(send, {"error": "not found"}, 404)
160
+
161
+ async def send_json(self, send, data, status=200):
162
+ body = json.dumps(data).encode("utf-8")
163
+ await send({
164
+ "type": "http.response.start",
165
+ "status": status,
166
+ "headers": [
167
+ (b"content-type", b"application/json; charset=utf-8"),
168
+ (b"content-length", str(len(body)).encode()),
169
+ ],
170
+ })
171
+ await send({"type": "http.response.body", "body": body})
172
+
173
+ async def send_html(self, send, html, status=200):
174
+ body = html.encode("utf-8")
175
+ await send({
176
+ "type": "http.response.start",
177
+ "status": status,
178
+ "headers": [
179
+ (b"content-type", b"text/html; charset=utf-8"),
180
+ (b"content-length", str(len(body)).encode()),
181
+ ],
182
+ })
183
+ await send({"type": "http.response.body", "body": body})
184
+
185
+ async def send_response(self, send, response):
186
+ body = json.dumps(response.body).encode("utf-8")
187
+ headers = [
188
+ (b"content-type", b"application/json; charset=utf-8"),
189
+ (b"content-length", str(len(body)).encode()),
190
+ ]
191
+ headers.extend(response.headers)
192
+ await send({
193
+ "type": "http.response.start",
194
+ "status": response.status,
195
+ "headers": headers,
196
+ })
197
+ await send({"type": "http.response.body", "body": body})