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.

Files changed (57) hide show
  1. robyn/__init__.py +757 -0
  2. robyn/__main__.py +4 -0
  3. robyn/ai.py +308 -0
  4. robyn/argument_parser.py +129 -0
  5. robyn/authentication.py +96 -0
  6. robyn/cli.py +136 -0
  7. robyn/dependency_injection.py +71 -0
  8. robyn/env_populator.py +35 -0
  9. robyn/events.py +6 -0
  10. robyn/exceptions.py +32 -0
  11. robyn/jsonify.py +13 -0
  12. robyn/logger.py +80 -0
  13. robyn/mcp.py +461 -0
  14. robyn/openapi.py +448 -0
  15. robyn/processpool.py +226 -0
  16. robyn/py.typed +0 -0
  17. robyn/reloader.py +164 -0
  18. robyn/responses.py +208 -0
  19. robyn/robyn.cpython-311-darwin.so +0 -0
  20. robyn/robyn.pyi +421 -0
  21. robyn/router.py +410 -0
  22. robyn/scaffold/mongo/Dockerfile +12 -0
  23. robyn/scaffold/mongo/app.py +43 -0
  24. robyn/scaffold/mongo/requirements.txt +2 -0
  25. robyn/scaffold/no-db/Dockerfile +12 -0
  26. robyn/scaffold/no-db/app.py +12 -0
  27. robyn/scaffold/no-db/requirements.txt +1 -0
  28. robyn/scaffold/postgres/Dockerfile +32 -0
  29. robyn/scaffold/postgres/app.py +31 -0
  30. robyn/scaffold/postgres/requirements.txt +3 -0
  31. robyn/scaffold/postgres/supervisord.conf +14 -0
  32. robyn/scaffold/prisma/Dockerfile +15 -0
  33. robyn/scaffold/prisma/app.py +32 -0
  34. robyn/scaffold/prisma/requirements.txt +2 -0
  35. robyn/scaffold/prisma/schema.prisma +13 -0
  36. robyn/scaffold/sqlalchemy/Dockerfile +12 -0
  37. robyn/scaffold/sqlalchemy/__init__.py +0 -0
  38. robyn/scaffold/sqlalchemy/app.py +13 -0
  39. robyn/scaffold/sqlalchemy/models.py +21 -0
  40. robyn/scaffold/sqlalchemy/requirements.txt +2 -0
  41. robyn/scaffold/sqlite/Dockerfile +12 -0
  42. robyn/scaffold/sqlite/app.py +22 -0
  43. robyn/scaffold/sqlite/requirements.txt +1 -0
  44. robyn/scaffold/sqlmodel/Dockerfile +11 -0
  45. robyn/scaffold/sqlmodel/app.py +46 -0
  46. robyn/scaffold/sqlmodel/models.py +10 -0
  47. robyn/scaffold/sqlmodel/requirements.txt +2 -0
  48. robyn/status_codes.py +137 -0
  49. robyn/swagger.html +32 -0
  50. robyn/templating.py +30 -0
  51. robyn/types.py +44 -0
  52. robyn/ws.py +67 -0
  53. robyn-0.73.0.dist-info/METADATA +32 -0
  54. robyn-0.73.0.dist-info/RECORD +57 -0
  55. robyn-0.73.0.dist-info/WHEEL +4 -0
  56. robyn-0.73.0.dist-info/entry_points.txt +3 -0
  57. robyn-0.73.0.dist-info/licenses/LICENSE +25 -0
robyn/openapi.py ADDED
@@ -0,0 +1,448 @@
1
+ import inspect
2
+ import json
3
+ import typing
4
+ from dataclasses import asdict, dataclass, field
5
+ from importlib import resources
6
+ from inspect import Signature
7
+ from pathlib import Path
8
+ from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict
9
+
10
+ from robyn.responses import html
11
+ from robyn.robyn import QueryParams, Response
12
+ from robyn.types import Body
13
+
14
+
15
+ class str_typed_dict(TypedDict):
16
+ key: str
17
+ value: str
18
+
19
+
20
+ @dataclass
21
+ class Contact:
22
+ """
23
+ The contact information for the exposed API.
24
+ (https://swagger.io/specification/#contact-object)
25
+
26
+ @param name: Optional[str] The identifying name of the contact person/organization.
27
+ @param url: Optional[str] The URL pointing to the contact information. This MUST be in the form of a URL.
28
+ @param email: Optional[str] The email address of the contact person/organization. This MUST be in the form of an email address.
29
+ """
30
+
31
+ name: Optional[str] = None
32
+ url: Optional[str] = None
33
+ email: Optional[str] = None
34
+
35
+
36
+ @dataclass
37
+ class License:
38
+ """
39
+ The license information for the exposed API.
40
+ (https://swagger.io/specification/#license-object)
41
+
42
+ @param name: Optional[str] The license name used for the API.
43
+ @param url: Optional[str] A URL to the license used for the API. This MUST be in the form of a URL.
44
+ """
45
+
46
+ name: Optional[str] = None
47
+ url: Optional[str] = None
48
+
49
+
50
+ @dataclass
51
+ class Server:
52
+ """
53
+ An array of Server Objects, which provide connectivity information to a target server. If the servers property is
54
+ not provided, or is an empty array, the default value would be a Server Object with a url value of /.
55
+ (https://swagger.io/specification/#server-object)
56
+
57
+ @param url: str A URL to the target host. This URL supports Server Variables and MAY be relative,
58
+ to indicate that the host location is relative to the location where the OpenAPI document is being served.
59
+ Variable substitutions will be made when a variable is named in {brackets}.
60
+ @param description: Optional[str] An optional string describing the host designated by the URL.
61
+ """
62
+
63
+ url: str
64
+ description: Optional[str] = None
65
+
66
+
67
+ @dataclass
68
+ class ExternalDocumentation:
69
+ """
70
+ Additional external documentation for this operation.
71
+ (https://swagger.io/specification/#external-documentation-object)
72
+
73
+ @param description: Optional[str] A description of the target documentation.
74
+ @param url: Optional[str] The URL for the target documentation.
75
+ """
76
+
77
+ description: Optional[str] = None
78
+ url: Optional[str] = None
79
+
80
+
81
+ @dataclass
82
+ class Components:
83
+ """
84
+ Additional external documentation for this operation.
85
+ (https://swagger.io/specification/#components-object)
86
+
87
+ @param schemas: Optional[Dict[str, Dict]] An object to hold reusable Schema Objects.
88
+ @param responses: Optional[Dict[str, Dict]] An object to hold reusable Response Objects.
89
+ @param parameters: Optional[Dict[str, Dict]] An object to hold reusable Parameter Objects.
90
+ @param examples: Optional[Dict[str, Dict]] An object to hold reusable Example Objects.
91
+ @param requestBodies: Optional[Dict[str, Dict]] An object to hold reusable Request Body Objects.
92
+ @param securitySchemes: Optional[Dict[str, Dict]] An object to hold reusable Security Scheme Objects.
93
+ @param links: Optional[Dict[str, Dict]] An object to hold reusable Link Objects.
94
+ @param callbacks: Optional[Dict[str, Dict]] An object to hold reusable Callback Objects.
95
+ @param pathItems: Optional[Dict[str, Dict]] An object to hold reusable Callback Objects.
96
+ """
97
+
98
+ schemas: Optional[Dict[str, Dict]] = field(default_factory=dict)
99
+ responses: Optional[Dict[str, Dict]] = field(default_factory=dict)
100
+ parameters: Optional[Dict[str, Dict]] = field(default_factory=dict)
101
+ examples: Optional[Dict[str, Dict]] = field(default_factory=dict)
102
+ requestBodies: Optional[Dict[str, Dict]] = field(default_factory=dict)
103
+ securitySchemes: Optional[Dict[str, Dict]] = field(default_factory=dict)
104
+ links: Optional[Dict[str, Dict]] = field(default_factory=dict)
105
+ callbacks: Optional[Dict[str, Dict]] = field(default_factory=dict)
106
+ pathItems: Optional[Dict[str, Dict]] = field(default_factory=dict)
107
+
108
+
109
+ @dataclass
110
+ class OpenAPIInfo:
111
+ """
112
+ Provides metadata about the API. The metadata MAY be used by tooling as required.
113
+ (https://swagger.io/specification/#info-object)
114
+
115
+ @param title: str The title of the API.
116
+ @param version: str The version of the API.
117
+ @param description: Optional[str] A description of the API.
118
+ @param termsOfService: Optional[str] A URL to the Terms of Service for the API.
119
+ @param contact: Contact The contact information for the exposed API.
120
+ @param license: License The license information for the exposed API.
121
+ @param servers: list[Server] An list of Server objects representing the servers.
122
+ @param externalDocs: Optional[ExternalDocumentation] Additional external documentation.
123
+ @param components: Components An element to hold various schemas for the document.
124
+ """
125
+
126
+ title: str = "Robyn API"
127
+ version: str = "1.0.0"
128
+ description: Optional[str] = None
129
+ termsOfService: Optional[str] = None
130
+ contact: Contact = field(default_factory=Contact)
131
+ license: License = field(default_factory=License)
132
+ servers: List[Server] = field(default_factory=list)
133
+ externalDocs: Optional[ExternalDocumentation] = field(default_factory=ExternalDocumentation)
134
+ components: Components = field(default_factory=Components)
135
+
136
+
137
+ @dataclass
138
+ class OpenAPI:
139
+ """
140
+ This is the root object of the OpenAPI document.
141
+
142
+ @param info: OpenAPIInfo Provides metadata about the API.
143
+ @param openapi_spec: dict The content of openapi.json as a dict
144
+ """
145
+
146
+ info: OpenAPIInfo = field(default_factory=OpenAPIInfo)
147
+ openapi_spec: dict = field(init=False)
148
+ openapi_file_override: bool = False # denotes whether there is an override or not.
149
+
150
+ def __post_init__(self):
151
+ """
152
+ Initializes the openapi_spec dict
153
+ """
154
+ if self.openapi_file_override:
155
+ return
156
+
157
+ self.openapi_spec = {
158
+ "openapi": "3.1.0",
159
+ "info": asdict(self.info),
160
+ "paths": {},
161
+ "components": asdict(self.info.components),
162
+ "servers": [asdict(server) for server in self.info.servers],
163
+ "externalDocs": asdict(self.info.externalDocs) if self.info.externalDocs.url else None,
164
+ }
165
+
166
+ def add_openapi_path_obj(self, route_type: str, endpoint: str, openapi_name: str, openapi_tags: List[str], handler: Callable):
167
+ """
168
+ Adds the given path to openapi spec
169
+
170
+ @param route_type: str the http method as string (get, post ...)
171
+ @param endpoint: str the endpoint to be added
172
+ @param openapi_name: str the name of the endpoint
173
+ @param openapi_tags: List[str] for grouping of endpoints
174
+ @param handler: Callable the handler function for the endpoint
175
+ """
176
+
177
+ if self.openapi_file_override:
178
+ return
179
+
180
+ query_params = None
181
+ request_body = None
182
+ return_annotation = None
183
+
184
+ signature = inspect.signature(handler)
185
+ openapi_description = inspect.getdoc(handler) or ""
186
+
187
+ if signature:
188
+ parameters = signature.parameters
189
+
190
+ if "query_params" in parameters:
191
+ query_params = parameters["query_params"].default
192
+
193
+ if query_params is Signature.empty:
194
+ query_params = None
195
+
196
+ if "body" in parameters:
197
+ request_body = parameters["body"].default
198
+
199
+ if request_body is Signature.empty:
200
+ request_body = None
201
+
202
+ # priority to typing
203
+ for parameter in parameters:
204
+ param_annotation = parameters[parameter].annotation
205
+
206
+ if inspect.isclass(param_annotation):
207
+ if issubclass(param_annotation, Body):
208
+ request_body = param_annotation
209
+ elif issubclass(param_annotation, QueryParams):
210
+ query_params = param_annotation
211
+
212
+ if signature.return_annotation is not Signature.empty:
213
+ return_annotation = signature.return_annotation
214
+
215
+ modified_endpoint, path_obj = self.get_path_obj(
216
+ endpoint, openapi_name, openapi_description, openapi_tags, query_params, request_body, return_annotation
217
+ )
218
+
219
+ if modified_endpoint not in self.openapi_spec["paths"]:
220
+ self.openapi_spec["paths"][modified_endpoint] = {}
221
+ self.openapi_spec["paths"][modified_endpoint][route_type] = path_obj
222
+
223
+ def add_subrouter_paths(self, subrouter_openapi: "OpenAPI"):
224
+ """
225
+ Adds the subrouter paths to main router's openapi specs
226
+
227
+ @param subrouter_openapi: OpenAPI the OpenAPI object of the current subrouter
228
+ """
229
+
230
+ if self.openapi_file_override:
231
+ return
232
+
233
+ paths = subrouter_openapi.openapi_spec["paths"]
234
+
235
+ for path in paths:
236
+ self.openapi_spec["paths"][path] = paths[path]
237
+
238
+ def get_path_obj(
239
+ self,
240
+ endpoint: str,
241
+ name: str,
242
+ description: str,
243
+ tags: List[str],
244
+ query_params: Optional[str_typed_dict],
245
+ request_body: Optional[str_typed_dict],
246
+ return_annotation: Optional[str_typed_dict],
247
+ ) -> Tuple[str, dict]:
248
+ """
249
+ Get the "path" openapi object according to spec
250
+
251
+ @param endpoint: str the endpoint to be added
252
+ @param name: str the name of the endpoint
253
+ @param description: Optional[str] short description of the endpoint (to be fetched from the endpoint definition by default)
254
+ @param tags: List[str] for grouping of endpoints
255
+ @param query_params: Optional[TypedDict] query params for the function
256
+ @param request_body: Optional[TypedDict] request body for the function
257
+ @param return_annotation: Optional[TypedDict] return type of the endpoint handler
258
+
259
+ @return: (str, dict) a tuple containing the endpoint with path params wrapped in braces and the "path" openapi object
260
+ according to spec
261
+ """
262
+
263
+ if not description:
264
+ description = "No description provided"
265
+
266
+ openapi_path_object: dict = {
267
+ "summary": name,
268
+ "description": description,
269
+ "parameters": [],
270
+ "tags": tags,
271
+ }
272
+
273
+ # robyn has paths like /:url/:etc whereas openapi requires path like /{url}/{path}
274
+ # this function is used for converting path params to the required form
275
+ # initialized with endpoint for handling endpoints without path params
276
+ endpoint_with_path_params_wrapped_in_braces = endpoint
277
+
278
+ endpoint_path_params_split = endpoint.split(":")
279
+
280
+ if len(endpoint_path_params_split) > 1:
281
+ endpoint_without_path_params = endpoint_path_params_split[0]
282
+
283
+ endpoint_with_path_params_wrapped_in_braces = (
284
+ endpoint_without_path_params[:-1] if endpoint_without_path_params.endswith("/") else endpoint_without_path_params
285
+ )
286
+
287
+ for path_param in endpoint_path_params_split[1:]:
288
+ path_param_name = path_param[:-1] if path_param.endswith("/") else path_param
289
+
290
+ openapi_path_object["parameters"].append(
291
+ {
292
+ "name": path_param_name,
293
+ "in": "path",
294
+ "required": True,
295
+ "schema": {"type": "string"},
296
+ }
297
+ )
298
+ endpoint_with_path_params_wrapped_in_braces += "/{" + path_param_name + "}"
299
+
300
+ if query_params:
301
+ query_param_annotations = query_params.__annotations__ if query_params is str_typed_dict else typing.get_type_hints(query_params)
302
+
303
+ for query_param in query_param_annotations:
304
+ query_param_type = self.get_openapi_type(query_param_annotations[query_param])
305
+
306
+ openapi_path_object["parameters"].append(
307
+ {
308
+ "name": query_param,
309
+ "in": "query",
310
+ "required": False,
311
+ "schema": {"type": query_param_type},
312
+ }
313
+ )
314
+
315
+ if request_body:
316
+ properties = {}
317
+
318
+ request_body_annotations = request_body.__annotations__ if request_body is TypedDict else typing.get_type_hints(request_body)
319
+
320
+ for body_item in request_body_annotations:
321
+ properties[body_item] = self.get_schema_object(body_item, request_body_annotations[body_item])
322
+
323
+ request_body_object = {
324
+ "content": {
325
+ "application/json": {
326
+ "schema": {
327
+ "type": "object",
328
+ "properties": properties,
329
+ }
330
+ }
331
+ }
332
+ }
333
+
334
+ openapi_path_object["requestBody"] = request_body_object
335
+
336
+ response_schema: dict = {}
337
+ response_type = "text/plain"
338
+
339
+ if return_annotation and return_annotation is not Response:
340
+ response_type = "application/json"
341
+ response_schema = self.get_schema_object("response object", return_annotation)
342
+
343
+ openapi_path_object["responses"] = {"200": {"description": "Successful Response", "content": {response_type: {"schema": response_schema}}}}
344
+
345
+ return endpoint_with_path_params_wrapped_in_braces, openapi_path_object
346
+
347
+ def get_openapi_type(self, typed_dict: str_typed_dict) -> str:
348
+ """
349
+ Get actual type from the TypedDict annotations
350
+
351
+ @param typed_dict: TypedDict The TypedDict to be converted
352
+ @return: str the type inferred
353
+ """
354
+ type_mapping = {
355
+ int: "integer",
356
+ str: "string",
357
+ bool: "boolean",
358
+ float: "number",
359
+ dict: "object",
360
+ list: "array",
361
+ }
362
+
363
+ for type_name in type_mapping:
364
+ if typed_dict is type_name:
365
+ return type_mapping[type_name]
366
+
367
+ # default to "string" if type is not found
368
+ return "string"
369
+
370
+ def get_schema_object(self, parameter: str, param_type: Any) -> dict:
371
+ """
372
+ Get the schema object for request/response body
373
+
374
+ @param parameter: name of the parameter
375
+ @param param_type: Any the type to be inferred
376
+ @return: dict the properties object
377
+ """
378
+
379
+ properties: dict = {
380
+ "title": parameter.capitalize(),
381
+ }
382
+
383
+ type_mapping: dict = {
384
+ int: "integer",
385
+ str: "string",
386
+ bool: "boolean",
387
+ float: "number",
388
+ dict: "object",
389
+ list: "array",
390
+ }
391
+
392
+ for type_name in type_mapping:
393
+ if param_type is type_name:
394
+ properties["type"] = type_mapping[type_name]
395
+ return properties
396
+
397
+ # Check if it's a generic type (like List[Object])
398
+ if hasattr(param_type, "__origin__"):
399
+ if param_type.__origin__ is list or param_type.__origin__ is List:
400
+ properties["type"] = "array"
401
+ # Handle the element type in the list
402
+ if hasattr(param_type, "__args__") and param_type.__args__:
403
+ item_type = param_type.__args__[0]
404
+ properties["items"] = self.get_schema_object(f"{parameter}_item", item_type)
405
+ return properties
406
+
407
+ # check for Optional type
408
+ if param_type.__module__ == "typing":
409
+ properties["anyOf"] = [{"type": self.get_openapi_type(param_type.__args__[0])}, {"type": "null"}]
410
+ return properties
411
+ # check for custom classes and TypedDicts
412
+ elif inspect.isclass(param_type):
413
+ properties["type"] = "object"
414
+
415
+ properties["properties"] = {}
416
+
417
+ for e in param_type.__annotations__:
418
+ properties["properties"][e] = self.get_schema_object(e, param_type.__annotations__[e])
419
+
420
+ properties["type"] = "object"
421
+
422
+ return properties
423
+
424
+ def override_openapi(self, openapi_json_spec_path: Path):
425
+ """
426
+ Set a pre-configured OpenAPI spec
427
+ @param openapi_json_spec_path: str the path to the json file
428
+ """
429
+ with open(openapi_json_spec_path) as json_file:
430
+ json_file_content = json.load(json_file)
431
+ self.openapi_spec = dict(json_file_content)
432
+ self.openapi_file_override = True
433
+
434
+ def get_openapi_docs_page(self) -> Response:
435
+ """
436
+ Handler to the swagger html page to be deployed to the endpoint `/docs`
437
+ @return: FileResponse the swagger html page
438
+ """
439
+ with resources.open_text("robyn", "swagger.html") as path:
440
+ html_file = path.read()
441
+ return html(html_file)
442
+
443
+ def get_openapi_config(self) -> dict:
444
+ """
445
+ Returns the openapi spec as a dict
446
+ @return: dict the openapi spec
447
+ """
448
+ return self.openapi_spec
robyn/processpool.py ADDED
@@ -0,0 +1,226 @@
1
+ import asyncio
2
+ import signal
3
+ import sys
4
+ import webbrowser
5
+ from typing import Dict, List, Optional
6
+
7
+ from multiprocess import Process # type: ignore
8
+
9
+ from robyn.events import Events
10
+ from robyn.logger import logger
11
+ from robyn.robyn import FunctionInfo, Headers, Server, SocketHeld
12
+ from robyn.router import GlobalMiddleware, Route, RouteMiddleware
13
+ from robyn.types import Directory
14
+ from robyn.ws import WebSocket
15
+
16
+
17
+ def run_processes(
18
+ url: str,
19
+ port: int,
20
+ directories: List[Directory],
21
+ request_headers: Headers,
22
+ routes: List[Route],
23
+ global_middlewares: List[GlobalMiddleware],
24
+ route_middlewares: List[RouteMiddleware],
25
+ web_sockets: Dict[str, WebSocket],
26
+ event_handlers: Dict[Events, FunctionInfo],
27
+ workers: int,
28
+ processes: int,
29
+ response_headers: Headers,
30
+ excluded_response_headers_paths: Optional[List[str]],
31
+ open_browser: bool,
32
+ client_timeout: int = 30,
33
+ keep_alive_timeout: int = 20,
34
+ ) -> List[Process]:
35
+ socket = SocketHeld(url, port)
36
+
37
+ process_pool = init_processpool(
38
+ directories,
39
+ request_headers,
40
+ routes,
41
+ global_middlewares,
42
+ route_middlewares,
43
+ web_sockets,
44
+ event_handlers,
45
+ socket,
46
+ workers,
47
+ processes,
48
+ response_headers,
49
+ excluded_response_headers_paths,
50
+ client_timeout,
51
+ keep_alive_timeout,
52
+ )
53
+
54
+ def terminating_signal_handler(_sig, _frame):
55
+ logger.info("Terminating server!!", bold=True)
56
+ for process in process_pool:
57
+ process.kill()
58
+
59
+ signal.signal(signal.SIGINT, terminating_signal_handler)
60
+ signal.signal(signal.SIGTERM, terminating_signal_handler)
61
+
62
+ if open_browser:
63
+ logger.info("Opening browser...")
64
+ webbrowser.open_new_tab(f"http://{url}:{port}/")
65
+
66
+ logger.info("Press Ctrl + C to stop \n")
67
+ for process in process_pool:
68
+ process.join()
69
+
70
+ return process_pool
71
+
72
+
73
+ def init_processpool(
74
+ directories: List[Directory],
75
+ request_headers: Headers,
76
+ routes: List[Route],
77
+ global_middlewares: List[GlobalMiddleware],
78
+ route_middlewares: List[RouteMiddleware],
79
+ web_sockets: Dict[str, WebSocket],
80
+ event_handlers: Dict[Events, FunctionInfo],
81
+ socket: SocketHeld,
82
+ workers: int,
83
+ processes: int,
84
+ response_headers: Headers,
85
+ excluded_response_headers_paths: Optional[List[str]],
86
+ client_timeout: int = 30,
87
+ keep_alive_timeout: int = 20,
88
+ ) -> List[Process]:
89
+ process_pool: List = []
90
+ if sys.platform.startswith("win32") or processes == 1:
91
+ spawn_process(
92
+ directories,
93
+ request_headers,
94
+ routes,
95
+ global_middlewares,
96
+ route_middlewares,
97
+ web_sockets,
98
+ event_handlers,
99
+ socket,
100
+ workers,
101
+ response_headers,
102
+ excluded_response_headers_paths,
103
+ client_timeout,
104
+ keep_alive_timeout,
105
+ )
106
+
107
+ return process_pool
108
+
109
+ for _ in range(processes):
110
+ copied_socket = socket.try_clone()
111
+ process = Process(
112
+ target=spawn_process,
113
+ args=(
114
+ directories,
115
+ request_headers,
116
+ routes,
117
+ global_middlewares,
118
+ route_middlewares,
119
+ web_sockets,
120
+ event_handlers,
121
+ copied_socket,
122
+ workers,
123
+ response_headers,
124
+ excluded_response_headers_paths,
125
+ client_timeout,
126
+ keep_alive_timeout,
127
+ ),
128
+ )
129
+ process.start()
130
+ process_pool.append(process)
131
+
132
+ return process_pool
133
+
134
+
135
+ def initialize_event_loop():
136
+ if sys.platform.startswith("win32") or sys.platform.startswith("linux-cross"):
137
+ loop = asyncio.new_event_loop()
138
+ asyncio.set_event_loop(loop)
139
+ return loop
140
+ else:
141
+ # uv loop doesn't support windows or arm machines at the moment
142
+ # but uv loop is much faster than native asyncio
143
+ import uvloop
144
+
145
+ uvloop.install()
146
+ loop = uvloop.new_event_loop()
147
+ asyncio.set_event_loop(loop)
148
+ return loop
149
+
150
+
151
+ def spawn_process(
152
+ directories: List[Directory],
153
+ request_headers: Headers,
154
+ routes: List[Route],
155
+ global_middlewares: List[GlobalMiddleware],
156
+ route_middlewares: List[RouteMiddleware],
157
+ web_sockets: Dict[str, WebSocket],
158
+ event_handlers: Dict[Events, FunctionInfo],
159
+ socket: SocketHeld,
160
+ workers: int,
161
+ response_headers: Headers,
162
+ excluded_response_headers_paths: Optional[List[str]],
163
+ client_timeout: int = 30,
164
+ keep_alive_timeout: int = 20,
165
+ ):
166
+ """
167
+ This function is called by the main process handler to create a server runtime.
168
+ This functions allows one runtime per process.
169
+
170
+ :param directories List: the list of all the directories and related data
171
+ :param headers tuple: All the global headers in a tuple
172
+ :param routes Tuple[Route]: The routes tuple, containing the description about every route.
173
+ :param middlewares Tuple[Route]: The middleware routes tuple, containing the description about every route.
174
+ :param web_sockets list: This is a list of all the web socket routes
175
+ :param event_handlers Dict: This is an event dict that contains the event handlers
176
+ :param socket SocketHeld: This is the main tcp socket, which is being shared across multiple processes.
177
+ :param process_name string: This is the name given to the process to identify the process
178
+ :param workers int: This is the name given to the process to identify the process
179
+ """
180
+
181
+ loop = initialize_event_loop()
182
+
183
+ server = Server()
184
+
185
+ # TODO: if we remove the dot access
186
+ # the startup time will improve in the server
187
+ for directory in directories:
188
+ server.add_directory(*directory.as_list())
189
+
190
+ server.apply_request_headers(request_headers)
191
+
192
+ server.apply_response_headers(response_headers)
193
+
194
+ server.set_response_headers_exclude_paths(excluded_response_headers_paths)
195
+
196
+ for route in routes:
197
+ route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags = route
198
+ server.add_route(route_type, endpoint, function, is_const)
199
+
200
+ for middleware_type, middleware_function in global_middlewares:
201
+ server.add_global_middleware(middleware_type, middleware_function)
202
+
203
+ for middleware_type, endpoint, function, route_type in route_middlewares:
204
+ server.add_middleware_route(middleware_type, endpoint, function, route_type)
205
+
206
+ if Events.STARTUP in event_handlers:
207
+ server.add_startup_handler(event_handlers[Events.STARTUP])
208
+
209
+ if Events.SHUTDOWN in event_handlers:
210
+ server.add_shutdown_handler(event_handlers[Events.SHUTDOWN])
211
+
212
+ for endpoint in web_sockets:
213
+ web_socket = web_sockets[endpoint]
214
+ server.add_web_socket_route(
215
+ endpoint,
216
+ web_socket.methods["connect"],
217
+ web_socket.methods["close"],
218
+ web_socket.methods["message"],
219
+ )
220
+
221
+ try:
222
+ server.start(socket, workers)
223
+ loop = asyncio.get_event_loop()
224
+ loop.run_forever()
225
+ except KeyboardInterrupt:
226
+ loop.close()
robyn/py.typed ADDED
File without changes