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/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from robyn.cli import run
2
+
3
+ if __name__ == "__main__":
4
+ run()
robyn/ai.py ADDED
@@ -0,0 +1,308 @@
1
+ """
2
+ This is an experimental AI integration module for Robyn framework.
3
+
4
+ A poc for the blog at https://sanskar.wtf/posts/the-future-of-robyn
5
+
6
+ Provides agent and memory functionality for building AI-powered applications for the demonstration of the vision mentioned in
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ from abc import ABC, abstractmethod
12
+ from typing import Any, Dict, List, Optional, Union
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class AIConfig:
18
+ """Configuration class for AI providers and settings"""
19
+
20
+ def __init__(self, **kwargs):
21
+ self.config = kwargs
22
+ self._load_from_env()
23
+
24
+ def _load_from_env(self):
25
+ """Load configuration from environment variables"""
26
+ env_vars = {
27
+ "OPENAI_API_KEY": "openai_api_key",
28
+ "ANTHROPIC_API_KEY": "anthropic_api_key",
29
+ "GOOGLE_API_KEY": "google_api_key",
30
+ "AI_MODEL": "model",
31
+ "AI_TEMPERATURE": "temperature",
32
+ "AI_MAX_TOKENS": "max_tokens",
33
+ }
34
+
35
+ for env_var, config_key in env_vars.items():
36
+ if env_var in os.environ and config_key not in self.config:
37
+ value = os.environ[env_var]
38
+ # Convert numeric values
39
+ if config_key in ["temperature", "max_tokens"]:
40
+ try:
41
+ value = float(value) if config_key == "temperature" else int(value)
42
+ except ValueError:
43
+ pass
44
+ self.config[config_key] = value
45
+
46
+ def get(self, key: str, default: Any = None) -> Any:
47
+ """Get configuration value"""
48
+ return self.config.get(key, default)
49
+
50
+ def set(self, key: str, value: Any) -> None:
51
+ """Set configuration value"""
52
+ self.config[key] = value
53
+
54
+ def update(self, **kwargs) -> None:
55
+ """Update configuration with new values"""
56
+ self.config.update(kwargs)
57
+
58
+ def to_dict(self) -> Dict[str, Any]:
59
+ """Get configuration as dictionary"""
60
+ return self.config.copy()
61
+
62
+
63
+ class MemoryProvider(ABC):
64
+ """Abstract base class for memory providers"""
65
+
66
+ @abstractmethod
67
+ async def store(self, user_id: str, data: Dict[str, Any]) -> None:
68
+ """Store data in memory"""
69
+ pass
70
+
71
+ @abstractmethod
72
+ async def retrieve(self, user_id: str, query: Optional[str] = None) -> List[Dict[str, Any]]:
73
+ """Retrieve data from memory"""
74
+ pass
75
+
76
+ @abstractmethod
77
+ async def clear(self, user_id: str) -> None:
78
+ """Clear memory for a user"""
79
+ pass
80
+
81
+
82
+ class InMemoryProvider(MemoryProvider):
83
+ """Simple in-memory storage provider"""
84
+
85
+ def __init__(self):
86
+ self._storage: Dict[str, List[Dict[str, Any]]] = {}
87
+
88
+ async def store(self, user_id: str, data: Dict[str, Any]) -> None:
89
+ if user_id not in self._storage:
90
+ self._storage[user_id] = []
91
+ self._storage[user_id].append(data)
92
+
93
+ async def retrieve(self, user_id: str, query: Optional[str] = None) -> List[Dict[str, Any]]:
94
+ return self._storage.get(user_id, [])
95
+
96
+ async def clear(self, user_id: str) -> None:
97
+ if user_id in self._storage:
98
+ del self._storage[user_id]
99
+
100
+
101
+ class Memory:
102
+ """Memory interface for storing and retrieving conversation history and context"""
103
+
104
+ def __init__(self, provider: Union[str, MemoryProvider], user_id: str, **kwargs):
105
+ self.user_id = user_id
106
+
107
+ if isinstance(provider, str):
108
+ if provider == "inmemory":
109
+ self.provider = InMemoryProvider()
110
+ else:
111
+ raise ValueError(f"Unknown memory provider: {provider}")
112
+ else:
113
+ self.provider = provider
114
+
115
+ async def add(self, message: str, metadata: Optional[Dict[str, Any]] = None) -> None:
116
+ """Add a message to memory"""
117
+ data = {"message": message, "metadata": metadata or {}}
118
+ await self.provider.store(self.user_id, data)
119
+
120
+ async def get(self, query: Optional[str] = None) -> List[Dict[str, Any]]:
121
+ """Get messages from memory"""
122
+ return await self.provider.retrieve(self.user_id, query)
123
+
124
+ async def clear(self) -> None:
125
+ """Clear all memory for this user"""
126
+ await self.provider.clear(self.user_id)
127
+
128
+
129
+ class AgentRunner(ABC):
130
+ """Abstract base class for agent runners"""
131
+
132
+ @abstractmethod
133
+ async def run(self, query: str, **kwargs) -> Dict[str, Any]:
134
+ """Execute the agent with the given query"""
135
+ pass
136
+
137
+
138
+ class SimpleRunner(AgentRunner):
139
+ """Simple runner with OpenAI integration and fallback responses"""
140
+
141
+ def __init__(self, **config):
142
+ self.config = AIConfig(**config)
143
+ self._client = None
144
+
145
+ def _get_client(self):
146
+ """Get OpenAI client if API key is available"""
147
+ if self._client is None and self.config.get("openai_api_key"):
148
+ try:
149
+ import openai
150
+
151
+ self._client = openai.OpenAI(api_key=self.config.get("openai_api_key"))
152
+ except ImportError:
153
+ raise ImportError("openai package not installed. Install with: pip install openai")
154
+ return self._client
155
+
156
+ async def run(self, query: str, **kwargs) -> Dict[str, Any]:
157
+ """Execute with OpenAI (requires API key)"""
158
+ client = self._get_client()
159
+
160
+ if not client:
161
+ raise ValueError("OpenAI API key is required. Set 'openai_api_key' in configuration.")
162
+
163
+ try:
164
+ # Use OpenAI
165
+ messages = [{"role": "user", "content": query}]
166
+
167
+ # Add conversation history
168
+ context = kwargs.get("context", {})
169
+ history = context.get("history", [])
170
+
171
+ if history:
172
+ for item in history[-10:]:
173
+ msg = item.get("message", str(item))
174
+ if msg.startswith("Query: "):
175
+ messages.insert(-1, {"role": "user", "content": msg[7:]})
176
+ elif msg.startswith("Response: "):
177
+ messages.insert(-1, {"role": "assistant", "content": msg[10:]})
178
+
179
+ response = client.chat.completions.create(
180
+ model=self.config.get("model", "gpt-4o"),
181
+ messages=messages,
182
+ temperature=self.config.get("temperature", 0.7),
183
+ max_tokens=self.config.get("max_tokens", 1000),
184
+ )
185
+
186
+ content = response.choices[0].message.content
187
+ metadata = {
188
+ "runner_type": "simple_openai",
189
+ "model": self.config.get("model", "gpt-4o"),
190
+ "usage": {
191
+ "prompt_tokens": response.usage.prompt_tokens,
192
+ "completion_tokens": response.usage.completion_tokens,
193
+ "total_tokens": response.usage.total_tokens,
194
+ },
195
+ }
196
+
197
+ if self.config.get("debug", False):
198
+ metadata["debug_info"] = {"config": self.config.to_dict()}
199
+
200
+ return {"response": content, "query": query, "metadata": metadata}
201
+
202
+ except Exception as e:
203
+ logger.error(f"Error in SimpleRunner: {e}")
204
+ raise e
205
+
206
+
207
+ class Agent:
208
+ """AI Agent interface for handling queries with memory and execution"""
209
+
210
+ def __init__(self, runner: Union[str, AgentRunner], memory: Optional[Memory] = None, **kwargs):
211
+ self.memory = memory
212
+
213
+ if isinstance(runner, str):
214
+ if runner == "simple":
215
+ self.runner = SimpleRunner(**kwargs)
216
+ else:
217
+ raise ValueError(f"Unknown runner type: {runner}")
218
+ else:
219
+ self.runner = runner
220
+
221
+ async def run(self, query: str, history: bool = False, **kwargs) -> Dict[str, Any]:
222
+ """Run the agent with the given query"""
223
+ context = {}
224
+
225
+ if self.memory and history:
226
+ context["history"] = await self.memory.get()
227
+
228
+ # Add context to kwargs
229
+ if context:
230
+ kwargs["context"] = context
231
+
232
+ # Execute the agent
233
+ result = await self.runner.run(query, **kwargs)
234
+
235
+ # Store the interaction in memory if available
236
+ if self.memory:
237
+ await self.memory.add(f"Query: {query}")
238
+ if "response" in result:
239
+ await self.memory.add(f"Response: {result['response']}")
240
+
241
+ return result
242
+
243
+
244
+ def memory(provider: str = "inmemory", user_id: str = "default", **kwargs) -> Memory:
245
+ """
246
+ Create a memory instance with the specified provider
247
+
248
+ Args:
249
+ provider: Memory provider type ("inmemory")
250
+ user_id: User identifier for memory isolation
251
+ **kwargs: Additional configuration for the provider
252
+
253
+ Returns:
254
+ Memory instance
255
+ """
256
+ return Memory(provider=provider, user_id=user_id, **kwargs)
257
+
258
+
259
+ def configure(**kwargs) -> AIConfig:
260
+ """
261
+ Create and configure AI settings
262
+
263
+ Args:
264
+ **kwargs: Configuration options including API keys, model settings, etc.
265
+
266
+ Returns:
267
+ AIConfig instance
268
+
269
+ Example:
270
+ config = configure(
271
+ openai_api_key="your-key",
272
+ model="gpt-4",
273
+ temperature=0.7
274
+ )
275
+ """
276
+ return AIConfig(**kwargs)
277
+
278
+
279
+ def agent(runner: str = "simple", memory: Optional[Memory] = None, config: Optional[AIConfig] = None, **kwargs) -> Agent:
280
+ """
281
+ Create an agent instance with the specified runner
282
+
283
+ Args:
284
+ runner: Agent runner type ("simple")
285
+ memory: Optional memory instance for context
286
+ config: Optional AIConfig instance for configuration
287
+ **kwargs: Additional configuration for the runner
288
+
289
+ Returns:
290
+ Agent instance
291
+
292
+ Example:
293
+ # Simple usage
294
+ chat = agent()
295
+
296
+ # With configuration
297
+ config = configure(openai_api_key="your-key")
298
+ chat = agent(runner="simple", config=config)
299
+
300
+ # With memory
301
+ mem = memory(provider="inmemory", user_id="user123")
302
+ chat = agent(runner="simple", memory=mem)
303
+ """
304
+ # Merge config if provided
305
+ if config:
306
+ kwargs.update(config.to_dict())
307
+
308
+ return Agent(runner=runner, memory=memory, **kwargs)
@@ -0,0 +1,129 @@
1
+ import argparse
2
+ import os
3
+
4
+
5
+ class Config:
6
+ def __init__(self) -> None:
7
+ parser = argparse.ArgumentParser(description="Robyn, a fast async web framework with a rust runtime.")
8
+ self.parser = parser
9
+ parser.add_argument(
10
+ "--processes",
11
+ type=int,
12
+ default=None,
13
+ required=False,
14
+ help="Choose the number of processes. [Default: 1]",
15
+ )
16
+ parser.add_argument(
17
+ "--workers",
18
+ type=int,
19
+ default=None,
20
+ required=False,
21
+ help="Choose the number of workers. [Default: 1]",
22
+ )
23
+ parser.add_argument(
24
+ "--dev",
25
+ dest="dev",
26
+ action="store_true",
27
+ default=None,
28
+ help="Development mode. It restarts the server based on file changes.",
29
+ )
30
+ parser.add_argument(
31
+ "--log-level",
32
+ dest="log_level",
33
+ default=None,
34
+ help="Set the log level name",
35
+ )
36
+ parser.add_argument(
37
+ "--create",
38
+ action="store_true",
39
+ default=False,
40
+ help="Create a new project template.",
41
+ )
42
+ parser.add_argument(
43
+ "--docs",
44
+ action="store_true",
45
+ default=False,
46
+ help="Open the Robyn documentation.",
47
+ )
48
+ parser.add_argument(
49
+ "--open-browser",
50
+ action="store_true",
51
+ default=False,
52
+ help="Open the browser on successful start.",
53
+ )
54
+ parser.add_argument(
55
+ "--version",
56
+ action="store_true",
57
+ default=False,
58
+ help="Show the Robyn version.",
59
+ )
60
+ parser.add_argument(
61
+ "--compile-rust-path",
62
+ dest="compile_rust_path",
63
+ default=None,
64
+ help="Compile rust files in the given path.",
65
+ )
66
+
67
+ parser.add_argument(
68
+ "--create-rust-file",
69
+ dest="create_rust_file",
70
+ default=None,
71
+ help="Create a rust file with the given name.",
72
+ )
73
+ parser.add_argument(
74
+ "--disable-openapi",
75
+ dest="disable_openapi",
76
+ action="store_true",
77
+ default=False,
78
+ help="Disable the OpenAPI documentation.",
79
+ )
80
+ parser.add_argument(
81
+ "--fast",
82
+ dest="fast",
83
+ action="store_true",
84
+ default=False,
85
+ help="Fast mode. It sets the optimal values for processes, workers and log level. However, you can override them.",
86
+ )
87
+
88
+ args, unknown_args = parser.parse_known_args()
89
+ self.fast = args.fast
90
+ self.dev = args.dev
91
+ self.processes = args.processes
92
+ self.workers = args.workers
93
+ self.create = args.create
94
+ self.docs = args.docs
95
+ self.open_browser = args.open_browser
96
+ self.version = args.version
97
+ self.compile_rust_path = args.compile_rust_path
98
+ self.create_rust_file = args.create_rust_file
99
+ self.file_path = None
100
+ self.disable_openapi = args.disable_openapi
101
+ self.log_level = args.log_level
102
+
103
+ if self.fast:
104
+ # doing this here before every other check
105
+ # so that processes, workers and log_level can be overridden
106
+ cpu_count: int = os.cpu_count() or 1
107
+ self.processes = self.processes or ((cpu_count * 2) + 1) or 1
108
+ self.workers = self.workers or 2
109
+ self.log_level = self.log_level or "WARNING"
110
+
111
+ self.processes = self.processes or 1
112
+ self.workers = self.workers or 1
113
+
114
+ # find something that ends with .py in unknown_args
115
+ for arg in unknown_args:
116
+ if arg.endswith(".py"):
117
+ self.file_path = arg
118
+ break
119
+
120
+ if self.fast and self.dev:
121
+ raise Exception("--fast and --dev shouldn't be used together")
122
+
123
+ if self.dev and (self.processes != 1 or self.workers != 1):
124
+ raise Exception("--processes and --workers shouldn't be used with --dev")
125
+
126
+ if self.dev and self.log_level is None:
127
+ self.log_level = "DEBUG"
128
+ elif self.log_level is None:
129
+ self.log_level = "INFO"
@@ -0,0 +1,96 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+
4
+ from robyn.robyn import Headers, Identity, Request, Response
5
+ from robyn.status_codes import HTTP_401_UNAUTHORIZED
6
+
7
+
8
+ class AuthenticationNotConfiguredError(Exception):
9
+ """
10
+ This exception is raised when the authentication is not configured.
11
+ """
12
+
13
+ def __str__(self):
14
+ return "Authentication is not configured. Use app.configure_authentication() to configure it."
15
+
16
+
17
+ class TokenGetter(ABC):
18
+ @property
19
+ def scheme(self) -> str:
20
+ """
21
+ Gets the scheme of the token.
22
+ :return: The scheme of the token.
23
+ """
24
+ return self.__class__.__name__
25
+
26
+ @classmethod
27
+ @abstractmethod
28
+ def get_token(cls, request: Request) -> Optional[str]:
29
+ """
30
+ Gets the token from the request.
31
+ This method should not decode the token. Decoding is the role of the authentication handler.
32
+ :param request: The request object.
33
+ :return: The encoded token.
34
+ """
35
+ raise NotImplementedError()
36
+
37
+ @classmethod
38
+ @abstractmethod
39
+ def set_token(cls, request: Request, token: str):
40
+ """
41
+ Sets the token in the request.
42
+ This method should not encode the token. Encoding is the role of the authentication handler.
43
+ :param request: The request object.
44
+ :param token: The encoded token.
45
+ """
46
+ raise NotImplementedError()
47
+
48
+
49
+ class AuthenticationHandler(ABC):
50
+ def __init__(self, token_getter: TokenGetter):
51
+ """
52
+ Creates a new instance of the AuthenticationHandler class.
53
+ This class is an abstract class used to authenticate a user.
54
+ :param token_getter: The token getter used to get the token from the request.
55
+ """
56
+ self.token_getter = token_getter
57
+
58
+ @property
59
+ def unauthorized_response(self) -> Response:
60
+ return Response(
61
+ headers=Headers({"WWW-Authenticate": self.token_getter.scheme}),
62
+ description="Unauthorized",
63
+ status_code=HTTP_401_UNAUTHORIZED,
64
+ )
65
+
66
+ @abstractmethod
67
+ def authenticate(self, request: Request) -> Optional[Identity]:
68
+ """
69
+ Authenticates the user.
70
+ :param request: The request object.
71
+ :return: The identity of the user.
72
+ """
73
+ raise NotImplementedError()
74
+
75
+
76
+ class BearerGetter(TokenGetter):
77
+ """
78
+ This class is used to get the token from the Authorization header.
79
+ The scheme of the header must be Bearer.
80
+ """
81
+
82
+ @classmethod
83
+ def get_token(cls, request: Request) -> Optional[str]:
84
+ if request.headers.contains("authorization"):
85
+ authorization_header = request.headers.get("authorization")
86
+ else:
87
+ authorization_header = None
88
+
89
+ if not authorization_header or not authorization_header.startswith("Bearer "):
90
+ return None
91
+
92
+ return authorization_header[7:] # Remove the "Bearer " prefix
93
+
94
+ @classmethod
95
+ def set_token(cls, request: Request, token: str):
96
+ request.headers["Authorization"] = f"Bearer {token}"
robyn/cli.py ADDED
@@ -0,0 +1,136 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import sys
5
+ import webbrowser
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from InquirerPy.base.control import Choice
10
+ from InquirerPy.resolver import prompt
11
+
12
+ from robyn.env_populator import load_vars
13
+ from robyn.robyn import get_version
14
+
15
+ from .argument_parser import Config
16
+ from .reloader import create_rust_file, setup_reloader
17
+
18
+ SCAFFOLD_DIR = Path(__file__).parent / "scaffold"
19
+ CURRENT_WORKING_DIR = Path.cwd()
20
+
21
+
22
+ def create_robyn_app():
23
+ questions = [
24
+ {
25
+ "type": "input",
26
+ "message": "Directory Path:",
27
+ "name": "directory",
28
+ },
29
+ {
30
+ "type": "list",
31
+ "message": "Need Docker? (Y/N)",
32
+ "choices": [
33
+ Choice("Y", name="Y"),
34
+ Choice("N", name="N"),
35
+ ],
36
+ "default": Choice("N", name="N"),
37
+ "name": "docker",
38
+ },
39
+ {
40
+ "type": "list",
41
+ "message": "Please select project type (Mongo/Postgres/Sqlalchemy/Prisma): ",
42
+ "choices": [
43
+ Choice("no-db", name="No DB"),
44
+ Choice("sqlite", name="Sqlite"),
45
+ Choice("postgres", name="Postgres"),
46
+ Choice("mongo", name="MongoDB"),
47
+ Choice("sqlalchemy", name="SqlAlchemy"),
48
+ Choice("prisma", name="Prisma"),
49
+ Choice("sqlmodel", name="SQLModel"),
50
+ ],
51
+ "default": Choice("no-db", name="No DB"),
52
+ "name": "project_type",
53
+ },
54
+ ]
55
+ result = prompt(questions=questions)
56
+ project_dir_path = Path(str(result["directory"])).resolve()
57
+ docker = result["docker"]
58
+ project_type = str(result["project_type"])
59
+
60
+ final_project_dir_path = (CURRENT_WORKING_DIR / project_dir_path).resolve()
61
+
62
+ print(f"Creating a new Robyn project '{final_project_dir_path}'...")
63
+
64
+ # Create a new directory for the project
65
+ os.makedirs(final_project_dir_path, exist_ok=True)
66
+
67
+ selected_project_template = (SCAFFOLD_DIR / Path(project_type)).resolve()
68
+ shutil.copytree(str(selected_project_template), str(final_project_dir_path), dirs_exist_ok=True)
69
+
70
+ # If docker is not needed, delete the docker file
71
+ if docker == "N":
72
+ os.remove(f"{final_project_dir_path}/Dockerfile")
73
+
74
+ print(f"New Robyn project created in '{final_project_dir_path}' ")
75
+
76
+
77
+ def docs():
78
+ print("Opening Robyn documentation... | Offline docs coming soon!")
79
+ webbrowser.open("https://robyn.tech")
80
+
81
+
82
+ def start_dev_server(config: Config, file_path: Optional[str] = None):
83
+ if file_path is None:
84
+ return
85
+
86
+ absolute_file_path = (Path.cwd() / file_path).resolve()
87
+ directory_path = absolute_file_path.parent
88
+
89
+ if config.dev and not os.environ.get("IS_RELOADER_RUNNING", False):
90
+ setup_reloader(str(directory_path), str(absolute_file_path))
91
+ return
92
+
93
+
94
+ def start_app_normally(config: Config):
95
+ command = [sys.executable]
96
+
97
+ for arg in sys.argv[1:]:
98
+ command.append(arg)
99
+
100
+ # Run the subprocess
101
+ subprocess.run(command, start_new_session=False)
102
+
103
+
104
+ def run():
105
+ config = Config()
106
+
107
+ if not config.file_path:
108
+ config.file_path = f"{os.getcwd()}/{__name__}"
109
+
110
+ load_vars(project_root=os.path.dirname(os.path.abspath(config.file_path)))
111
+ os.environ["ROBYN_CLI"] = "True"
112
+
113
+ if config.dev is None:
114
+ config.dev = os.getenv("ROBYN_DEV_MODE", False) == "True"
115
+
116
+ if config.create:
117
+ create_robyn_app()
118
+
119
+ elif file_name := config.create_rust_file:
120
+ create_rust_file(file_name)
121
+
122
+ elif config.version:
123
+ print(get_version())
124
+
125
+ elif config.docs:
126
+ docs()
127
+
128
+ elif config.dev:
129
+ print("Starting dev server...")
130
+ start_dev_server(config, config.file_path)
131
+ else:
132
+ try:
133
+ start_app_normally(config)
134
+ except KeyboardInterrupt:
135
+ # for the crash happening upon pressing Ctrl + C
136
+ pass