robyn 0.73.0__cp311-cp311-macosx_10_12_x86_64.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.
Potentially problematic release.
This version of robyn might be problematic. Click here for more details.
- robyn/__init__.py +757 -0
- robyn/__main__.py +4 -0
- robyn/ai.py +308 -0
- robyn/argument_parser.py +129 -0
- robyn/authentication.py +96 -0
- robyn/cli.py +136 -0
- robyn/dependency_injection.py +71 -0
- robyn/env_populator.py +35 -0
- robyn/events.py +6 -0
- robyn/exceptions.py +32 -0
- robyn/jsonify.py +13 -0
- robyn/logger.py +80 -0
- robyn/mcp.py +461 -0
- robyn/openapi.py +448 -0
- robyn/processpool.py +226 -0
- robyn/py.typed +0 -0
- robyn/reloader.py +164 -0
- robyn/responses.py +208 -0
- robyn/robyn.cpython-311-darwin.so +0 -0
- robyn/robyn.pyi +421 -0
- robyn/router.py +410 -0
- robyn/scaffold/mongo/Dockerfile +12 -0
- robyn/scaffold/mongo/app.py +43 -0
- robyn/scaffold/mongo/requirements.txt +2 -0
- robyn/scaffold/no-db/Dockerfile +12 -0
- robyn/scaffold/no-db/app.py +12 -0
- robyn/scaffold/no-db/requirements.txt +1 -0
- robyn/scaffold/postgres/Dockerfile +32 -0
- robyn/scaffold/postgres/app.py +31 -0
- robyn/scaffold/postgres/requirements.txt +3 -0
- robyn/scaffold/postgres/supervisord.conf +14 -0
- robyn/scaffold/prisma/Dockerfile +15 -0
- robyn/scaffold/prisma/app.py +32 -0
- robyn/scaffold/prisma/requirements.txt +2 -0
- robyn/scaffold/prisma/schema.prisma +13 -0
- robyn/scaffold/sqlalchemy/Dockerfile +12 -0
- robyn/scaffold/sqlalchemy/__init__.py +0 -0
- robyn/scaffold/sqlalchemy/app.py +13 -0
- robyn/scaffold/sqlalchemy/models.py +21 -0
- robyn/scaffold/sqlalchemy/requirements.txt +2 -0
- robyn/scaffold/sqlite/Dockerfile +12 -0
- robyn/scaffold/sqlite/app.py +22 -0
- robyn/scaffold/sqlite/requirements.txt +1 -0
- robyn/scaffold/sqlmodel/Dockerfile +11 -0
- robyn/scaffold/sqlmodel/app.py +46 -0
- robyn/scaffold/sqlmodel/models.py +10 -0
- robyn/scaffold/sqlmodel/requirements.txt +2 -0
- robyn/status_codes.py +137 -0
- robyn/swagger.html +32 -0
- robyn/templating.py +30 -0
- robyn/types.py +44 -0
- robyn/ws.py +67 -0
- robyn-0.73.0.dist-info/METADATA +32 -0
- robyn-0.73.0.dist-info/RECORD +57 -0
- robyn-0.73.0.dist-info/WHEEL +4 -0
- robyn-0.73.0.dist-info/entry_points.txt +3 -0
- robyn-0.73.0.dist-info/licenses/LICENSE +25 -0
robyn/router.py
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from types import CoroutineType
|
|
6
|
+
from typing import Callable, Dict, List, NamedTuple, Optional, Union
|
|
7
|
+
|
|
8
|
+
from robyn import status_codes
|
|
9
|
+
from robyn.authentication import AuthenticationHandler, AuthenticationNotConfiguredError
|
|
10
|
+
from robyn.dependency_injection import DependencyMap
|
|
11
|
+
from robyn.jsonify import jsonify
|
|
12
|
+
from robyn.openapi import OpenAPI
|
|
13
|
+
from robyn.responses import FileResponse, StreamingResponse
|
|
14
|
+
from robyn.robyn import FunctionInfo, Headers, HttpMethod, Identity, MiddlewareType, QueryParams, Request, Response, Url
|
|
15
|
+
from robyn.types import Body, Files, FormData, IPAddress, Method, PathParams
|
|
16
|
+
from robyn.ws import WebSocket
|
|
17
|
+
|
|
18
|
+
_logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def lower_http_method(method: HttpMethod):
|
|
22
|
+
return (str(method))[11:].lower()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Route(NamedTuple):
|
|
26
|
+
route_type: HttpMethod
|
|
27
|
+
route: str
|
|
28
|
+
function: FunctionInfo
|
|
29
|
+
is_const: bool
|
|
30
|
+
auth_required: bool
|
|
31
|
+
openapi_name: str
|
|
32
|
+
openapi_tags: List[str]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RouteMiddleware(NamedTuple):
|
|
36
|
+
middleware_type: MiddlewareType
|
|
37
|
+
route: str
|
|
38
|
+
function: FunctionInfo
|
|
39
|
+
route_type: HttpMethod
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class GlobalMiddleware(NamedTuple):
|
|
43
|
+
middleware_type: MiddlewareType
|
|
44
|
+
function: FunctionInfo
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BaseRouter(ABC):
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def add_route(*args) -> Union[Callable, CoroutineType, WebSocket]: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Router(BaseRouter):
|
|
53
|
+
def __init__(self) -> None:
|
|
54
|
+
super().__init__()
|
|
55
|
+
self.routes: List[Route] = []
|
|
56
|
+
|
|
57
|
+
def _format_tuple_response(self, res: tuple) -> Response:
|
|
58
|
+
if len(res) != 3:
|
|
59
|
+
raise ValueError("Tuple should have 3 elements")
|
|
60
|
+
|
|
61
|
+
description, headers, status_code = res
|
|
62
|
+
description = self._format_response(description).description
|
|
63
|
+
new_headers: Headers = Headers(headers)
|
|
64
|
+
if new_headers.contains("Content-Type"):
|
|
65
|
+
headers.set("Content-Type", new_headers.get("Content-Type"))
|
|
66
|
+
|
|
67
|
+
return Response(
|
|
68
|
+
status_code=status_code,
|
|
69
|
+
headers=headers,
|
|
70
|
+
description=description,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def _format_response(
|
|
74
|
+
self,
|
|
75
|
+
res: Union[Dict, Response, StreamingResponse, bytes, tuple, str],
|
|
76
|
+
) -> Union[Response, StreamingResponse]:
|
|
77
|
+
if isinstance(res, Response):
|
|
78
|
+
return res
|
|
79
|
+
|
|
80
|
+
if isinstance(res, StreamingResponse):
|
|
81
|
+
return res
|
|
82
|
+
|
|
83
|
+
if isinstance(res, dict):
|
|
84
|
+
return Response(
|
|
85
|
+
status_code=status_codes.HTTP_200_OK,
|
|
86
|
+
headers=Headers({"Content-Type": "application/json"}),
|
|
87
|
+
description=jsonify(res),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if isinstance(res, FileResponse):
|
|
91
|
+
response: Response = Response(
|
|
92
|
+
status_code=res.status_code,
|
|
93
|
+
headers=res.headers,
|
|
94
|
+
description=res.file_path,
|
|
95
|
+
)
|
|
96
|
+
response.file_path = res.file_path
|
|
97
|
+
return response
|
|
98
|
+
|
|
99
|
+
if isinstance(res, bytes):
|
|
100
|
+
return Response(
|
|
101
|
+
status_code=status_codes.HTTP_200_OK,
|
|
102
|
+
headers=Headers({"Content-Type": "application/octet-stream"}),
|
|
103
|
+
description=res,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if isinstance(res, tuple):
|
|
107
|
+
return self._format_tuple_response(tuple(res))
|
|
108
|
+
|
|
109
|
+
return Response(
|
|
110
|
+
status_code=status_codes.HTTP_200_OK,
|
|
111
|
+
headers=Headers({"Content-Type": "text/plain"}),
|
|
112
|
+
description=str(res).encode("utf-8"),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def add_route( # type: ignore
|
|
116
|
+
self,
|
|
117
|
+
route_type: HttpMethod,
|
|
118
|
+
endpoint: str,
|
|
119
|
+
handler: Callable,
|
|
120
|
+
is_const: bool,
|
|
121
|
+
auth_required: bool,
|
|
122
|
+
openapi_name: str,
|
|
123
|
+
openapi_tags: List[str],
|
|
124
|
+
exception_handler: Optional[Callable],
|
|
125
|
+
injected_dependencies: dict,
|
|
126
|
+
) -> Union[Callable, CoroutineType]:
|
|
127
|
+
def wrapped_handler(*args, **kwargs):
|
|
128
|
+
# In the execute functions the request is passed into *args
|
|
129
|
+
request = next(filter(lambda it: isinstance(it, Request), args), None)
|
|
130
|
+
|
|
131
|
+
handler_params = inspect.signature(handler).parameters
|
|
132
|
+
|
|
133
|
+
if not request or (len(handler_params) == 1 and next(iter(handler_params)) is Request):
|
|
134
|
+
return handler(*args, **kwargs)
|
|
135
|
+
|
|
136
|
+
type_mapping = {
|
|
137
|
+
"request": Request,
|
|
138
|
+
"query_params": QueryParams,
|
|
139
|
+
"headers": Headers,
|
|
140
|
+
"path_params": PathParams,
|
|
141
|
+
"body": Body,
|
|
142
|
+
"method": Method,
|
|
143
|
+
"url": Url,
|
|
144
|
+
"form_data": FormData,
|
|
145
|
+
"files": Files,
|
|
146
|
+
"ip_addr": IPAddress,
|
|
147
|
+
"identity": Identity,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type_filtered_params = {}
|
|
151
|
+
|
|
152
|
+
for handler_param in iter(handler_params):
|
|
153
|
+
for type_name in type_mapping:
|
|
154
|
+
handler_param_type = handler_params[handler_param].annotation
|
|
155
|
+
handler_param_name = handler_params[handler_param].name
|
|
156
|
+
if handler_param_type is Request:
|
|
157
|
+
type_filtered_params[handler_param_name] = request
|
|
158
|
+
elif handler_param_type is type_mapping[type_name]:
|
|
159
|
+
type_filtered_params[handler_param_name] = getattr(request, type_name)
|
|
160
|
+
elif inspect.isclass(handler_param_type):
|
|
161
|
+
if issubclass(handler_param_type, Body):
|
|
162
|
+
type_filtered_params[handler_param_name] = getattr(request, "body")
|
|
163
|
+
elif issubclass(handler_param_type, QueryParams):
|
|
164
|
+
type_filtered_params[handler_param_name] = getattr(request, "query_params")
|
|
165
|
+
|
|
166
|
+
request_components = {
|
|
167
|
+
"r": request,
|
|
168
|
+
"req": request,
|
|
169
|
+
"request": request,
|
|
170
|
+
"query_params": request.query_params,
|
|
171
|
+
"headers": request.headers,
|
|
172
|
+
"path_params": request.path_params,
|
|
173
|
+
"body": request.body,
|
|
174
|
+
"method": request.method,
|
|
175
|
+
"url": request.url,
|
|
176
|
+
"ip_addr": request.ip_addr,
|
|
177
|
+
"identity": request.identity,
|
|
178
|
+
"form_data": request.form_data,
|
|
179
|
+
"files": request.files,
|
|
180
|
+
"router_dependencies": injected_dependencies["router_dependencies"],
|
|
181
|
+
"global_dependencies": injected_dependencies["global_dependencies"],
|
|
182
|
+
**kwargs,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
name_filtered_params = {k: v for k, v in request_components.items() if k in handler_params and k not in type_filtered_params}
|
|
186
|
+
|
|
187
|
+
filtered_params = dict(**type_filtered_params, **name_filtered_params)
|
|
188
|
+
|
|
189
|
+
if len(filtered_params) != len(handler_params):
|
|
190
|
+
invalid_args = set(handler_params) - set(filtered_params)
|
|
191
|
+
raise SyntaxError(f"Unexpected request params found: {invalid_args}")
|
|
192
|
+
|
|
193
|
+
return handler(**filtered_params)
|
|
194
|
+
|
|
195
|
+
@wraps(handler)
|
|
196
|
+
async def async_inner_handler(*args, **kwargs):
|
|
197
|
+
try:
|
|
198
|
+
response = self._format_response(
|
|
199
|
+
await wrapped_handler(*args, **kwargs),
|
|
200
|
+
)
|
|
201
|
+
except Exception as err:
|
|
202
|
+
if exception_handler is None:
|
|
203
|
+
raise
|
|
204
|
+
response = self._format_response(
|
|
205
|
+
exception_handler(err),
|
|
206
|
+
)
|
|
207
|
+
return response
|
|
208
|
+
|
|
209
|
+
@wraps(handler)
|
|
210
|
+
def inner_handler(*args, **kwargs):
|
|
211
|
+
try:
|
|
212
|
+
response = self._format_response(
|
|
213
|
+
wrapped_handler(*args, **kwargs),
|
|
214
|
+
)
|
|
215
|
+
except Exception as err:
|
|
216
|
+
if exception_handler is None:
|
|
217
|
+
raise
|
|
218
|
+
response = self._format_response(
|
|
219
|
+
exception_handler(err),
|
|
220
|
+
)
|
|
221
|
+
return response
|
|
222
|
+
|
|
223
|
+
# these are the arguments
|
|
224
|
+
params = dict(inspect.signature(handler).parameters)
|
|
225
|
+
|
|
226
|
+
new_injected_dependencies = {}
|
|
227
|
+
for dependency in injected_dependencies:
|
|
228
|
+
if dependency in params:
|
|
229
|
+
new_injected_dependencies[dependency] = injected_dependencies[dependency]
|
|
230
|
+
else:
|
|
231
|
+
_logger.debug(f"Dependency {dependency} is not used in the handler {handler.__name__}")
|
|
232
|
+
|
|
233
|
+
if inspect.iscoroutinefunction(handler):
|
|
234
|
+
function = FunctionInfo(
|
|
235
|
+
async_inner_handler,
|
|
236
|
+
True,
|
|
237
|
+
len(params),
|
|
238
|
+
params,
|
|
239
|
+
new_injected_dependencies,
|
|
240
|
+
)
|
|
241
|
+
self.routes.append(Route(route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags))
|
|
242
|
+
return async_inner_handler
|
|
243
|
+
else:
|
|
244
|
+
function = FunctionInfo(
|
|
245
|
+
inner_handler,
|
|
246
|
+
False,
|
|
247
|
+
len(params),
|
|
248
|
+
params,
|
|
249
|
+
new_injected_dependencies,
|
|
250
|
+
)
|
|
251
|
+
self.routes.append(Route(route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags))
|
|
252
|
+
return inner_handler
|
|
253
|
+
|
|
254
|
+
def prepare_routes_openapi(self, openapi: OpenAPI, included_routers: List) -> None:
|
|
255
|
+
for route in self.routes:
|
|
256
|
+
openapi.add_openapi_path_obj(lower_http_method(route.route_type), route.route, route.openapi_name, route.openapi_tags, route.function.handler)
|
|
257
|
+
|
|
258
|
+
# TODO! after include_routes does not immediately merge all the routes
|
|
259
|
+
# for router in included_routers:
|
|
260
|
+
# for route in router:
|
|
261
|
+
# openapi.add_openapi_path_obj(lower_http_method(route.route_type), route.route, route.openapi_name, route.openapi_tags, route.function.handler)
|
|
262
|
+
|
|
263
|
+
def get_routes(self) -> List[Route]:
|
|
264
|
+
return self.routes
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class MiddlewareRouter(BaseRouter):
|
|
268
|
+
def __init__(self, dependencies: DependencyMap = DependencyMap()) -> None:
|
|
269
|
+
super().__init__()
|
|
270
|
+
self.global_middlewares: List[GlobalMiddleware] = []
|
|
271
|
+
self.route_middlewares: List[RouteMiddleware] = []
|
|
272
|
+
self.authentication_handler: Optional[AuthenticationHandler] = None
|
|
273
|
+
self.dependencies = dependencies
|
|
274
|
+
|
|
275
|
+
def set_authentication_handler(self, authentication_handler: AuthenticationHandler):
|
|
276
|
+
self.authentication_handler = authentication_handler
|
|
277
|
+
|
|
278
|
+
def add_route( # type: ignore
|
|
279
|
+
self,
|
|
280
|
+
middleware_type: MiddlewareType,
|
|
281
|
+
endpoint: str,
|
|
282
|
+
route_type: HttpMethod,
|
|
283
|
+
handler: Callable,
|
|
284
|
+
injected_dependencies: dict,
|
|
285
|
+
) -> Callable:
|
|
286
|
+
params = dict(inspect.signature(handler).parameters)
|
|
287
|
+
|
|
288
|
+
new_injected_dependencies = {}
|
|
289
|
+
for dependency in injected_dependencies:
|
|
290
|
+
if dependency in params:
|
|
291
|
+
new_injected_dependencies[dependency] = injected_dependencies[dependency]
|
|
292
|
+
else:
|
|
293
|
+
_logger.debug(f"Dependency {dependency} is not used in the middleware handler {handler.__name__}")
|
|
294
|
+
|
|
295
|
+
function = FunctionInfo(
|
|
296
|
+
handler,
|
|
297
|
+
inspect.iscoroutinefunction(handler),
|
|
298
|
+
len(params),
|
|
299
|
+
params,
|
|
300
|
+
new_injected_dependencies,
|
|
301
|
+
)
|
|
302
|
+
self.route_middlewares.append(RouteMiddleware(middleware_type, endpoint, function, route_type))
|
|
303
|
+
return handler
|
|
304
|
+
|
|
305
|
+
def add_auth_middleware(self, endpoint: str, route_type: HttpMethod):
|
|
306
|
+
"""
|
|
307
|
+
This method adds an authentication middleware to the specified endpoint.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
injected_dependencies: dict = {}
|
|
311
|
+
|
|
312
|
+
def decorator(handler):
|
|
313
|
+
@wraps(handler)
|
|
314
|
+
def inner_handler(request: Request, *args):
|
|
315
|
+
if not self.authentication_handler:
|
|
316
|
+
raise AuthenticationNotConfiguredError()
|
|
317
|
+
identity = self.authentication_handler.authenticate(request)
|
|
318
|
+
if identity is None:
|
|
319
|
+
return self.authentication_handler.unauthorized_response
|
|
320
|
+
request.identity = identity
|
|
321
|
+
|
|
322
|
+
return request
|
|
323
|
+
|
|
324
|
+
self.add_route(
|
|
325
|
+
MiddlewareType.BEFORE_REQUEST,
|
|
326
|
+
endpoint,
|
|
327
|
+
route_type,
|
|
328
|
+
inner_handler,
|
|
329
|
+
injected_dependencies,
|
|
330
|
+
)
|
|
331
|
+
return inner_handler
|
|
332
|
+
|
|
333
|
+
return decorator
|
|
334
|
+
|
|
335
|
+
# These inner functions are basically a wrapper around the closure(decorator) being returned.
|
|
336
|
+
# They take a handler, convert it into a closure and return the arguments.
|
|
337
|
+
# Arguments are returned as they could be modified by the middlewares.
|
|
338
|
+
def add_middleware(self, middleware_type: MiddlewareType, endpoint: Optional[str]) -> Callable[..., None]:
|
|
339
|
+
# no dependency injection here
|
|
340
|
+
injected_dependencies: dict = {}
|
|
341
|
+
|
|
342
|
+
def inner(handler):
|
|
343
|
+
@wraps(handler)
|
|
344
|
+
async def async_inner_handler(*args, **kwargs):
|
|
345
|
+
return await handler(*args, **kwargs)
|
|
346
|
+
|
|
347
|
+
@wraps(handler)
|
|
348
|
+
def inner_handler(*args, **kwargs):
|
|
349
|
+
return handler(*args, **kwargs)
|
|
350
|
+
|
|
351
|
+
if endpoint is not None:
|
|
352
|
+
if inspect.iscoroutinefunction(handler):
|
|
353
|
+
self.add_route(
|
|
354
|
+
middleware_type,
|
|
355
|
+
endpoint,
|
|
356
|
+
HttpMethod.GET,
|
|
357
|
+
async_inner_handler,
|
|
358
|
+
injected_dependencies,
|
|
359
|
+
)
|
|
360
|
+
else:
|
|
361
|
+
self.add_route(middleware_type, endpoint, HttpMethod.GET, inner_handler, injected_dependencies)
|
|
362
|
+
else:
|
|
363
|
+
params = dict(inspect.signature(handler).parameters)
|
|
364
|
+
|
|
365
|
+
if inspect.iscoroutinefunction(handler):
|
|
366
|
+
self.global_middlewares.append(
|
|
367
|
+
GlobalMiddleware(
|
|
368
|
+
middleware_type,
|
|
369
|
+
FunctionInfo(
|
|
370
|
+
async_inner_handler,
|
|
371
|
+
True,
|
|
372
|
+
len(params),
|
|
373
|
+
params,
|
|
374
|
+
injected_dependencies,
|
|
375
|
+
),
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
else:
|
|
379
|
+
self.global_middlewares.append(
|
|
380
|
+
GlobalMiddleware(
|
|
381
|
+
middleware_type,
|
|
382
|
+
FunctionInfo(
|
|
383
|
+
inner_handler,
|
|
384
|
+
False,
|
|
385
|
+
len(params),
|
|
386
|
+
params,
|
|
387
|
+
injected_dependencies,
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
return inner
|
|
393
|
+
|
|
394
|
+
def get_route_middlewares(self) -> List[RouteMiddleware]:
|
|
395
|
+
return self.route_middlewares
|
|
396
|
+
|
|
397
|
+
def get_global_middlewares(self) -> List[GlobalMiddleware]:
|
|
398
|
+
return self.global_middlewares
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class WebSocketRouter(BaseRouter):
|
|
402
|
+
def __init__(self) -> None:
|
|
403
|
+
super().__init__()
|
|
404
|
+
self.routes: dict = {}
|
|
405
|
+
|
|
406
|
+
def add_route(self, endpoint: str, web_socket: WebSocket) -> None: # type: ignore
|
|
407
|
+
self.routes[endpoint] = web_socket
|
|
408
|
+
|
|
409
|
+
def get_routes(self) -> Dict[str, WebSocket]:
|
|
410
|
+
return self.routes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from pymongo import MongoClient
|
|
2
|
+
|
|
3
|
+
from robyn import Robyn
|
|
4
|
+
|
|
5
|
+
app = Robyn(__file__)
|
|
6
|
+
db = MongoClient("URL HERE")
|
|
7
|
+
|
|
8
|
+
users = db.users # define a collection
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.get("/")
|
|
12
|
+
def index():
|
|
13
|
+
return "Hello World!"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# create a route
|
|
17
|
+
@app.get("/users")
|
|
18
|
+
async def get_users():
|
|
19
|
+
all_users = await users.find().to_list(length=None)
|
|
20
|
+
return {"users": all_users}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# create a route to add a new user
|
|
24
|
+
@app.post("/users")
|
|
25
|
+
async def add_user(request):
|
|
26
|
+
user_data = await request.json()
|
|
27
|
+
result = await users.insert_one(user_data)
|
|
28
|
+
return {"success": True, "inserted_id": str(result.inserted_id)}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# create a route to fetch a single user by ID
|
|
32
|
+
@app.get("/users/{user_id}")
|
|
33
|
+
async def get_user(request):
|
|
34
|
+
user_id = request.path_params["user_id"]
|
|
35
|
+
user = await users.find_one({"_id": user_id})
|
|
36
|
+
if user:
|
|
37
|
+
return user
|
|
38
|
+
else:
|
|
39
|
+
return {"error": "User not found"}, 404
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
app.start(host="0.0.0.0", port=8080)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
robyn
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ---- Build the PostgreSQL Base ----
|
|
2
|
+
FROM postgres:latest AS postgres-base
|
|
3
|
+
|
|
4
|
+
ENV POSTGRES_USER=postgres
|
|
5
|
+
ENV POSTGRES_PASSWORD=password
|
|
6
|
+
ENV POSTGRES_DB=postgresDB
|
|
7
|
+
|
|
8
|
+
# ---- Build the Python App ----
|
|
9
|
+
FROM python:3.11-bookworm
|
|
10
|
+
|
|
11
|
+
# Install supervisor
|
|
12
|
+
RUN apt-get update && apt-get install -y supervisor
|
|
13
|
+
|
|
14
|
+
WORKDIR /workspace
|
|
15
|
+
|
|
16
|
+
COPY . .
|
|
17
|
+
|
|
18
|
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Copy PostgreSQL binaries from the first stage
|
|
22
|
+
COPY --from=postgres-base /usr/local/bin /usr/local/bin
|
|
23
|
+
COPY --from=postgres-base /usr/lib/postgresql /usr/lib/postgresql
|
|
24
|
+
COPY --from=postgres-base /usr/share/postgresql /usr/share/postgresql
|
|
25
|
+
COPY --from=postgres-base /var/lib/postgresql /var/lib/postgresql
|
|
26
|
+
|
|
27
|
+
# Add supervisord config
|
|
28
|
+
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
|
29
|
+
|
|
30
|
+
EXPOSE 8080 5455
|
|
31
|
+
|
|
32
|
+
CMD ["/usr/bin/supervisord"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import psycopg2
|
|
2
|
+
|
|
3
|
+
from robyn import Robyn
|
|
4
|
+
|
|
5
|
+
DB_NAME = "postgresDB"
|
|
6
|
+
DB_HOST = "localhost"
|
|
7
|
+
DB_USER = "postgres"
|
|
8
|
+
DB_PASS = "password"
|
|
9
|
+
DB_PORT = "5455"
|
|
10
|
+
|
|
11
|
+
conn = psycopg2.connect(database=DB_NAME, host=DB_HOST, user=DB_USER, password=DB_PASS, port=DB_PORT)
|
|
12
|
+
|
|
13
|
+
app = Robyn(__file__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# create a route to fetch all users
|
|
17
|
+
@app.get("/users")
|
|
18
|
+
def get_users():
|
|
19
|
+
cursor = conn.cursor()
|
|
20
|
+
cursor.execute("SELECT * FROM users")
|
|
21
|
+
all_users = cursor.fetchall()
|
|
22
|
+
return {"users": all_users}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.get("/")
|
|
26
|
+
def index():
|
|
27
|
+
return "Hello World!"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
app.start(host="0.0.0.0", port=8080)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[supervisord]
|
|
2
|
+
nodaemon=true
|
|
3
|
+
|
|
4
|
+
[program:python-app]
|
|
5
|
+
command=python3 /workspace/app.py --log-level=DEBUG
|
|
6
|
+
autostart=true
|
|
7
|
+
autorestart=true
|
|
8
|
+
redirect_stderr=true
|
|
9
|
+
|
|
10
|
+
[program:postgres]
|
|
11
|
+
command=postgres -c 'config_file=/etc/postgresql/postgresql.conf'
|
|
12
|
+
autostart=true
|
|
13
|
+
autorestart=true
|
|
14
|
+
redirect_stderr=true
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
FROM python:3.11-bookworm
|
|
2
|
+
|
|
3
|
+
WORKDIR /workspace
|
|
4
|
+
|
|
5
|
+
COPY . .
|
|
6
|
+
|
|
7
|
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
RUN python3 -m prisma generate
|
|
11
|
+
RUN python3 -m prisma migrate dev --name init
|
|
12
|
+
|
|
13
|
+
EXPOSE 8080
|
|
14
|
+
|
|
15
|
+
CMD ["python3", "app.py", "--log-level=DEBUG"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from prisma import Prisma
|
|
2
|
+
from prisma.models import User
|
|
3
|
+
|
|
4
|
+
from robyn import Robyn
|
|
5
|
+
|
|
6
|
+
app = Robyn(__file__)
|
|
7
|
+
prisma = Prisma(auto_register=True)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.startup_handler
|
|
11
|
+
async def startup_handler() -> None:
|
|
12
|
+
await prisma.connect()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.shutdown_handler
|
|
16
|
+
async def shutdown_handler() -> None:
|
|
17
|
+
if prisma.is_connected():
|
|
18
|
+
await prisma.disconnect()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.get("/")
|
|
22
|
+
async def h():
|
|
23
|
+
user = await User.prisma().create(
|
|
24
|
+
data={
|
|
25
|
+
"name": "Robert",
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
return user.json(indent=2)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
app.start(host="0.0.0.0", port=8080)
|
|
File without changes
|