Skip to content

Commit

Permalink
Merge branch 'main' into feat-221
Browse files Browse the repository at this point in the history
  • Loading branch information
mikaeelghr authored Feb 11, 2023
2 parents 5733de8 + ee17878 commit 51d67eb
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 156 deletions.
136 changes: 47 additions & 89 deletions robyn/__init__.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,43 @@
import asyncio
import logging
import multiprocessing as mp
import multiprocess as mp
import os
import signal
import sys
from typing import Callable, List, Optional
from nestd import get_all_nested

from multiprocess import Process # type: ignore
from watchdog.observers import Observer

from robyn.argument_parser import ArgumentParser
from robyn.argument_parser import Config
from robyn.dev_event_handler import EventHandler
from robyn.env_populator import load_vars
from robyn.events import Events
from robyn.log_colors import Colors
from robyn.processpool import spawn_process
from robyn.logger import Colors, logger
from robyn.processpool import run_processes
from robyn.responses import jsonify, serve_file, serve_html
from robyn.robyn import FunctionInfo, SocketHeld
from robyn.robyn import FunctionInfo
from robyn.router import MiddlewareRouter, Router, WebSocketRouter
from robyn.types import Directory, Header
from robyn.ws import WS


logger = logging.getLogger(__name__)


class Robyn:
"""This is the python wrapper for the Robyn binaries."""

def __init__(self, file_object: str) -> None:
directory_path = os.path.dirname(os.path.abspath(file_object))
self.file_path = file_object
self.directory_path = directory_path
self.parser = ArgumentParser()
self.dev = self.parser.is_dev
self.processes = self.parser.num_processes
self.workers = self.parser.workers
self.log_level = self.parser.log_level
self.config = Config()
self.router = Router()
self.middleware_router = MiddlewareRouter()
self.web_socket_router = WebSocketRouter()
self.request_headers: List[Header] = [] # This needs a better type
self.directories: List[Directory] = []
self.event_handlers = {}
load_vars(project_root=directory_path)
self._config_logger()
logging.basicConfig(level=self.config.log_level)

def _add_route(self, route_type, endpoint, handler, is_const=False):
"""
Expand Down Expand Up @@ -113,85 +105,64 @@ def start(self, url: str = "127.0.0.1", port: int = 8080):
"""
Starts the server
:param port int: reperesents the port number at which the server is listening
:param port int: represents the port number at which the server is listening
"""

url = os.getenv("ROBYN_URL", url)
port = int(os.getenv("ROBYN_PORT", port))

logger.info(
"%sStarting server at %s:%s %s", Colors.OKGREEN, url, port, Colors.ENDC
)

def init_processpool(socket):
process_pool = []
if sys.platform.startswith("win32"):
spawn_process(
self.directories,
self.request_headers,
self.router.get_routes(),
self.middleware_router.get_routes(),
self.web_socket_router.get_routes(),
self.event_handlers,
socket,
workers,
)

return process_pool

for _ in range(self.processes):
copied_socket = socket.try_clone()
process = Process(
target=spawn_process,
args=(
self.directories,
self.request_headers,
self.router.get_routes(),
self.middleware_router.get_routes(),
self.web_socket_router.get_routes(),
self.event_handlers,
copied_socket,
workers,
),
)
process.start()
process_pool.append(process)

return process_pool
logger.info(f"Starting server at {url}:{port}")

mp.allow_connection_pickling()

if not self.dev:
workers = self.workers
socket = SocketHeld(url, port)

process_pool = init_processpool(socket)
if not self.config.dev:
run_processes(
url,
port,
self.directories,
self.request_headers,
self.router.get_routes(),
self.middleware_router.get_routes(),
self.web_socket_router.get_routes(),
self.event_handlers,
self.config.workers,
self.config.processes,
)
else:
event_handler = EventHandler(
url,
port,
self.directories,
self.request_headers,
self.router.get_routes(),
self.middleware_router.get_routes(),
self.web_socket_router.get_routes(),
self.event_handlers,
self.config.workers,
self.config.processes,
)
event_handler.start_server()
logger.info(
f"Dev server initialized with the directory_path : {self.directory_path}",
Colors.BLUE,
)

def terminating_signal_handler(_sig, _frame):
logger.info(
f"{Colors.BOLD}{Colors.OKGREEN} Terminating server!! {Colors.ENDC}"
)
for process in process_pool:
process.kill()
logger.info("Terminating server!!", bold=True)
event_handler.stop_server()
observer.stop()
observer.join()

signal.signal(signal.SIGINT, terminating_signal_handler)
signal.signal(signal.SIGTERM, terminating_signal_handler)

logger.info(f"{Colors.OKGREEN}Press Ctrl + C to stop \n{Colors.ENDC}")
for process in process_pool:
process.join()
else:
event_handler = EventHandler(self.file_path)
event_handler.start_server_first_time()
logger.info(
f"{Colors.OKBLUE}Dev server initialised with the directory_path : {self.directory_path}{Colors.ENDC}"
)
observer = Observer()
observer.schedule(event_handler, path=self.directory_path, recursive=True)
observer.start()

try:
while True:
pass
while observer.is_alive():
observer.join(1)
finally:
observer.stop()
observer.join()
Expand Down Expand Up @@ -338,18 +309,5 @@ def inner(handler):

return inner

def _config_logger(self):
"""
This is the method to configure the logger either on the dev mode or the env variable
"""

log_level = "WARN"

if self.dev:
log_level = "DEBUG"

log_level = self.log_level if self.log_level else log_level
logging.basicConfig(level=log_level)


__all__ = ["Robyn", "jsonify", "serve_file", "serve_html"]
44 changes: 19 additions & 25 deletions robyn/argument_parser.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,51 @@
import argparse


class ArgumentParser(argparse.ArgumentParser):
class Config:
def __init__(self) -> None:
self.parser = argparse.ArgumentParser(
parser = argparse.ArgumentParser(
description="Robyn, a fast async web framework with a rust runtime."
)
self.parser.add_argument(
parser.add_argument(
"--processes",
type=int,
default=1,
required=False,
help="Choose the number of processes. [Default: 1]",
)
self.parser.add_argument(
parser.add_argument(
"--workers",
type=int,
default=1,
required=False,
help="Choose the number of workers. [Default: 1]",
)
self.parser.add_argument(
parser.add_argument(
"--dev",
dest="dev",
action="store_true",
default=False,
help="Development mode. It restarts the server based on file changes.",
)

self.parser.add_argument(
parser.add_argument(
"--log-level",
dest="log_level",
default="INFO",
default=None,
help="Set the log level name",
)

self.args, unknown = self.parser.parse_known_args()

@property
def num_processes(self) -> int:
return self.args.processes
args, unknown = parser.parse_known_args()

@property
def workers(self) -> int:
return self.args.workers
self.processes = args.processes
self.workers = args.workers
self.dev = args.dev

@property
def log_level(self) -> str:
return self.args.log_level

@property
def is_dev(self) -> bool:
_is_dev = self.args.dev
if _is_dev and (self.num_processes != 1 or self.workers != 1):
if self.dev and (self.processes != 1 or self.workers != 1):
raise Exception("--processes and --workers shouldn't be used with --dev")
return _is_dev

if self.dev and args.log_level is None:
self.log_level = "DEBUG"
elif args.log_level is None:
self.log_level = "INFO"
else:
self.log_level = args.log_level
83 changes: 55 additions & 28 deletions robyn/dev_event_handler.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,70 @@
import subprocess
import sys
from typing import Dict, List

from watchdog.events import FileSystemEventHandler
from robyn.events import Events
from robyn.processpool import run_processes

from robyn.robyn import FunctionInfo
from robyn.router import MiddlewareRoute, Route
from robyn.types import Directory, Header
from robyn.ws import WS


class EventHandler(FileSystemEventHandler):
def __init__(self, file_name) -> None:
self.file_name = file_name
def __init__(
self,
url: str,
port: int,
directories: List[Directory],
request_headers: List[Header],
routes: List[Route],
middlewares: List[MiddlewareRoute],
web_sockets: Dict[str, WS],
event_handlers: Dict[Events, FunctionInfo],
workers: int,
processes: int,
) -> None:
self.url = url
self.port = port
self.directories = directories
self.request_headers = request_headers
self.routes = routes
self.middlewares = middlewares
self.web_sockets = web_sockets
self.event_handlers = event_handlers
self.n_workers = workers
self.n_processes = processes
self.processes = []
self.python_alias = (
"python3" if not sys.platform.startswith("win32") else "python"
)
self.shell = True if sys.platform.startswith("win32") else False

def start_server_first_time(self) -> None:
if self.processes:
raise Exception("Something wrong with the server")
self.processes.append(
subprocess.Popen(
[self.python_alias, self.file_name],
shell=self.shell,
start_new_session=False,
)

def start_server(self):
processes = run_processes(
self.url,
self.port,
self.directories,
self.request_headers,
self.routes,
self.middlewares,
self.web_sockets,
self.event_handlers,
self.n_workers,
self.n_processes,
True,
)

self.processes.extend(processes)

def stop_server(self):
for process in self.processes:
process.kill()

def on_any_event(self, event) -> None:
"""
This function is a callback that will start a new server on every even change
:param event FSEvent: a data structure with info about the events
"""

if len(self.processes) > 0:
for process in self.processes:
process.terminate()
self.processes.append(
subprocess.Popen(
[self.python_alias, self.file_name],
shell=self.shell,
start_new_session=False,
)
)
for process in self.processes:
process.kill()

self.start_server()
10 changes: 0 additions & 10 deletions robyn/log_colors.py

This file was deleted.

Loading

0 comments on commit 51d67eb

Please sign in to comment.