From 4a24b3640dd75f72dd0be923e77b16e08b4efbf4 Mon Sep 17 00:00:00 2001 From: KronosDev-Pro Date: Sat, 23 Nov 2024 21:17:38 +0000 Subject: [PATCH] lint & format --- reflex/config.py | 20 ++--- reflex/server/base.py | 156 +++++++++++++++++++++++++++++++++----- reflex/server/granian.py | 30 ++++++-- reflex/server/gunicorn.py | 31 ++++++-- reflex/server/uvicorn.py | 32 +++++--- 5 files changed, 216 insertions(+), 53 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index da381ae91..b98eab834 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -698,11 +698,7 @@ class Config(Base): # Custom Backend Server backend_server_prod: server.CustomBackendServer = server.GunicornBackendServer( - threads=2, - workers=4, - max_requests=100, - max_requests_jitter=25, - timeout=120 + threads=2, workers=4, max_requests=100, max_requests_jitter=25, timeout=120 ) backend_server_dev: server.CustomBackendServer = server.UvicornBackendServer( workers=1, @@ -737,15 +733,21 @@ class Config(Base): raise ConfigError( "REDIS_URL is required when using the redis state manager." ) - + if any( - getattr(self.get_fields().get(key, None), "default", None) == self.get_value(key) - for key in ("timeout","gunicorn_worker_class", "gunicorn_workers", "gunicorn_max_requests", "gunicorn_max_requests_jitter" ) + getattr(self.get_fields().get(key, None), "default", None) + == self.get_value(key) + for key in ( + "timeout", + "gunicorn_worker_class", + "gunicorn_workers", + "gunicorn_max_requests", + "gunicorn_max_requests_jitter", + ) ): console.warn( 'The following reflex configuration fields are obsolete: "timeout", "gunicorn_worker_class", "gunicorn_workers", "gunicorn_max_requests", "gunicorn_max_requests_jitter"\nplease update your configuration.' ) - @property def module(self) -> str: diff --git a/reflex/server/base.py b/reflex/server/base.py index b19a6acb5..c78c90224 100644 --- a/reflex/server/base.py +++ b/reflex/server/base.py @@ -7,7 +7,7 @@ from abc import abstractmethod from dataclasses import Field, dataclass from dataclasses import field as dc_field from pathlib import Path -from typing import Any, Callable, Sequence, ClassVar +from typing import Any, Callable, ClassVar, Sequence from reflex import constants from reflex.constants.base import Env, LogLevel @@ -26,6 +26,12 @@ class CliType: fmt: `'--env-file {value}'` value: `'/config.conf'` result => `'--env-file /config.conf'` + + Args: + fmt (str): format + + Returns: + ReturnCliTypeFn: function wrapper """ def wrapper(value: bool) -> str: @@ -46,6 +52,13 @@ class CliType: fmt: `'--reload'` value: `True` result => `'--reload'` + + Args: + fmt (str): format + bool_value (bool): boolean value used for toggle condition + + Returns: + ReturnCliTypeFn: function wrapper """ def wrapper(value: bool) -> str: @@ -80,6 +93,16 @@ class CliType: value: `True` toggle_value: `True` result => `'--no-access-log'` + + Args: + fmt (str): format + toggle_kw (str): keyword used when toggled. Defaults to "no". + toggle_sep (str): separator used when toggled. Defaults to "-". + toggle_value (bool): boolean value used for toggle condition. Defaults to False. + **kwargs: Keyword arguments to pass to the format string function. + + Returns: + ReturnCliTypeFn: function wrapper """ def wrapper(value: bool) -> str: @@ -102,6 +125,7 @@ class CliType: Example (Multiple args mode): fmt: `'--header {value}'`. data_list: `['X-Forwarded-Proto=https', 'X-Forwarded-For=0.0.0.0']` + join_sep: `None` result => `'--header \"X-Forwarded-Proto=https\" --header \"X-Forwarded-For=0.0.0.0\"'` Example (Single args mode): @@ -116,6 +140,14 @@ class CliType: join_sep (required): `';'` value_transformer: `lambda value: f'{value[0]}:{value[1]}'` result => `--headers \"X-Forwarded-Proto:https;X-Forwarded-For:0.0.0.0\"` + + Args: + fmt (str): format + join_sep (str): separator used + value_transformer (Callable[[Any], str]): function used for transformer the element + + Returns: + ReturnCliTypeFn: function wrapper """ def wrapper(values: Sequence[str]) -> str: @@ -139,7 +171,17 @@ def field_( exclude: bool = False, **kwargs, ): - """Custom dataclass field builder.""" + """Custom dataclass field builder. + + Args: + default (Any): default value. Defaults to None. + metadata_cli (ReturnCliTypeFn | None): cli wrapper function. Defaults to None. + exclude (bool): used for excluding the field to the server configuration (system field). Defaults to False. + **kwargs: Keyword arguments to pass to the field dataclasses function. + + Returns: + Field: return the field dataclasses + """ params_ = { "default": default, "metadata": {"cli": metadata_cli, "exclude": exclude}, @@ -156,16 +198,28 @@ def field_( class CustomBackendServer: """BackendServer base.""" - _env: ClassVar[Env] = field_(default=Env.DEV, metadata_cli=None, exclude=True, repr = False, init = False) - _app: ClassVar[Any] = field_(default=None, metadata_cli=None, exclude=True, repr = False, init = False) - _app_uri: ClassVar[str] = field_(default="", metadata_cli=None, exclude=True, repr = False, init = False) + _env: ClassVar[Env] = field_( + default=Env.DEV, metadata_cli=None, exclude=True, repr=False, init=False + ) + _app: ClassVar[Any] = field_( + default=None, metadata_cli=None, exclude=True, repr=False, init=False + ) + _app_uri: ClassVar[str] = field_( + default="", metadata_cli=None, exclude=True, repr=False, init=False + ) @staticmethod - def get_app_module(for_granian_target: bool = False, add_extra_api: bool = False): + def get_app_module( + for_granian_target: bool = False, add_extra_api: bool = False + ) -> str: """Get the app module for the backend. + Args: + for_granian_target (bool): make the return compatible with Granian. Defaults to False. + add_extra_api (bool): add the keyword "api" at the end (needed for Uvicorn & Granian). Defaults to False. + Returns: - The app module for the backend. + str: The app module for the backend. """ import reflex @@ -177,21 +231,41 @@ class CustomBackendServer: return f"{app_path}:{constants.CompileVars.APP}{f'.{constants.CompileVars.API}' if add_extra_api else ''}" def get_available_cpus(self) -> int: - """Get available cpus.""" + """Get available cpus. + + Returns: + int: number of available cpu cores + """ return os.cpu_count() or 1 def get_max_workers(self) -> int: - """Get max workers.""" + """Get maximum workers. + + Returns: + int: get the maximum number of workers + """ # https://docs.gunicorn.org/en/latest/settings.html#workers return (os.cpu_count() or 1) * 4 + 1 def get_recommended_workers(self) -> int: - """Get recommended workers.""" + """Get recommended workers. + + Returns: + int: get the recommended number of workers + """ # https://docs.gunicorn.org/en/latest/settings.html#workers return (os.cpu_count() or 1) * 2 + 1 def get_max_threads(self, wait_time_ms: int = 50, service_time_ms: int = 5) -> int: - """Get max threads.""" + """Get maximum threads. + + Args: + wait_time_ms (int): the mean waiting duration targeted. Defaults to 50. + service_time_ms (int): the mean working duration. Defaults to 5. + + Returns: + int: get the maximum number of threads + """ # https://engineering.zalando.com/posts/2019/04/how-to-set-an-ideal-thread-pool-size.html # Brian Goetz formula return int(self.get_available_cpus() * (1 + wait_time_ms / service_time_ms)) @@ -202,7 +276,16 @@ class CustomBackendServer: wait_time_ms: int = 50, service_time_ms: int = 5, ) -> int: - """Get recommended threads.""" + """Get recommended threads. + + Args: + target_reqs (int | None): number of requests targeted. Defaults to None. + wait_time_ms (int): the mean waiting duration targeted. Defaults to 50. + service_time_ms (int): the mean working duration. Defaults to 5. + + Returns: + int: get the recommended number of threads + """ # https://engineering.zalando.com/posts/2019/04/how-to-set-an-ideal-thread-pool-size.html max_available_threads = self.get_max_threads() @@ -221,19 +304,35 @@ class CustomBackendServer: ) def get_fields(self) -> dict[str, Field]: - """Return all the fields.""" + """Return all the fields. + + Returns: + dict[str, Field]: return the fields dictionary + """ return self.__dataclass_fields__ def get_values(self) -> dict[str, Any]: - """Return all values.""" + """Return all values. + + Returns: + dict[str, Any]: returns the value of the fields + """ return { key: getattr(self, key) for key, field in self.__dataclass_fields__.items() if field.metadata["exclude"] is False } - def is_default_value(self, key, value: Any | None = None) -> bool: - """Check if the `value` is the same value from default context.""" + def is_default_value(self, key: str, value: Any | None = None) -> bool: + """Check if the `value` is the same value from default context. + + Args: + key (str): the name of the field + value (Any | None, optional): the value to check if is equal to the default value. Defaults to None. + + Returns: + bool: result of the condition of value are equal to the default value + """ from dataclasses import MISSING field = self.get_fields()[key] @@ -250,9 +349,13 @@ class CustomBackendServer: @abstractmethod def get_backend_bind(self) -> tuple[str, int]: - """Return the backend host and port""" + """Return the backend host and port. + + Returns: + tuple[str, int]: The host address and port. + """ raise NotImplementedError() - + @abstractmethod def check_import(self): """Check package importation.""" @@ -260,12 +363,23 @@ class CustomBackendServer: @abstractmethod def setup(self, host: str, port: int, loglevel: LogLevel, env: Env): - """Setup.""" + """Setup. + + Args: + host (str): host address + port (int): port address + loglevel (LogLevel): log level + env (Env): prod/dev environment + """ raise NotImplementedError() @abstractmethod - def run_prod(self): - """Run in production mode.""" + def run_prod(self) -> list[str]: + """Run in production mode. + + Returns: + list[str]: Command ready to be executed + """ raise NotImplementedError() @abstractmethod diff --git a/reflex/server/granian.py b/reflex/server/granian.py index 547929398..a87faee93 100644 --- a/reflex/server/granian.py +++ b/reflex/server/granian.py @@ -192,6 +192,11 @@ class GranianBackendServer(CustomBackendServer): ) def get_backend_bind(self) -> tuple[str, int]: + """Return the backend host and port. + + Returns: + tuple[str, int]: The host address and port. + """ return self.address, self.port def check_import(self): @@ -215,13 +220,20 @@ class GranianBackendServer(CustomBackendServer): sys.exit() def setup(self, host: str, port: int, loglevel: LogLevel, env: Env): - """Setup.""" - self._app_uri = self.get_app_module(for_granian_target=True, add_extra_api=True) + """Setup. + + Args: + host (str): host address + port (int): port address + loglevel (LogLevel): log level + env (Env): prod/dev environment + """ + self._app_uri = self.get_app_module(for_granian_target=True, add_extra_api=True) # type: ignore self.log_level = loglevel.value # type: ignore self.address = host self.port = port self.interface = "asgi" # NOTE: prevent obvious error - self._env = env + self._env = env # type: ignore if env == Env.PROD: if self.workers == self.get_fields()["workers"].default: @@ -244,7 +256,11 @@ class GranianBackendServer(CustomBackendServer): self.reload_ignore_dirs = [".web"] def run_prod(self): - """Run in production mode.""" + """Run in production mode. + + Returns: + list[str]: Command ready to be executed + """ self.check_import() command = ["granian"] @@ -261,7 +277,7 @@ class GranianBackendServer(CustomBackendServer): def run_dev(self): """Run in development mode.""" self.check_import() - from granian import Granian + from granian import Granian # type: ignore exclude_keys = ( "http1_keep_alive", @@ -277,7 +293,8 @@ class GranianBackendServer(CustomBackendServer): "http2_max_headers_size", "http2_max_send_buffer_size", ) - self._app = Granian( + + self._app = Granian( # type: ignore **{ **{ key: value @@ -309,5 +326,6 @@ class GranianBackendServer(CustomBackendServer): self._app.serve() async def shutdown(self): + """Shutdown the backend server.""" if self._app and self._env == Env.DEV: self._app.shutdown() diff --git a/reflex/server/gunicorn.py b/reflex/server/gunicorn.py index be0b62729..5e47044cf 100644 --- a/reflex/server/gunicorn.py +++ b/reflex/server/gunicorn.py @@ -279,10 +279,14 @@ class GunicornBackendServer(CustomBackendServer): ) def get_backend_bind(self) -> tuple[str, int]: - """Return the backend host and port""" + """Return the backend host and port. + + Returns: + tuple[str, int]: The host address and port. + """ host, port = self.bind[0].split(":") return host, int(port) - + def check_import(self): """Check package importation.""" from importlib.util import find_spec @@ -304,11 +308,18 @@ class GunicornBackendServer(CustomBackendServer): sys.exit() def setup(self, host: str, port: int, loglevel: LogLevel, env: Env): - """Setup.""" - self._app_uri = f"{self.get_app_module()}()" + """Setup. + + Args: + host (str): host address + port (int): port address + loglevel (LogLevel): log level + env (Env): prod/dev environment + """ + self._app_uri = f"{self.get_app_module()}()" # type: ignore self.loglevel = loglevel.value # type: ignore self.bind = [f"{host}:{port}"] - self._env = env + self._env = env # type: ignore if env == Env.PROD: if self.workers == self.get_fields()["workers"].default: @@ -328,7 +339,11 @@ class GunicornBackendServer(CustomBackendServer): self.reload = True def run_prod(self) -> list[str]: - """Run in production mode.""" + """Run in production mode. + + Returns: + list[str]: Command ready to be executed + """ self.check_import() command = ["gunicorn"] @@ -376,13 +391,13 @@ class GunicornBackendServer(CustomBackendServer): def load(self): return gunicorn_import_app(self._app_uri) - + def stop(self): from gunicorn.arbiter import Arbiter Arbiter(self).stop() - self._app = StandaloneApplication(app_uri=self._app_uri, options=options_) + self._app = StandaloneApplication(app_uri=self._app_uri, options=options_) # type: ignore self._app.run() async def shutdown(self): diff --git a/reflex/server/uvicorn.py b/reflex/server/uvicorn.py index 7f96c6070..e164fefee 100644 --- a/reflex/server/uvicorn.py +++ b/reflex/server/uvicorn.py @@ -184,9 +184,13 @@ class UvicornBackendServer(CustomBackendServer): ) def get_backend_bind(self) -> tuple[str, int]: - """Return the backend host and port""" + """Return the backend host and port. + + Returns: + tuple[str, int]: The host address and port. + """ return self.host, self.port - + def check_import(self): """Check package importation.""" from importlib.util import find_spec @@ -210,12 +214,19 @@ class UvicornBackendServer(CustomBackendServer): sys.exit() def setup(self, host: str, port: int, loglevel: LogLevel, env: Env): - """Setup.""" - self._app_uri = self.get_app_module(add_extra_api=True) + """Setup. + + Args: + host (str): host address + port (int): port address + loglevel (LogLevel): log level + env (Env): prod/dev environment + """ + self._app_uri = self.get_app_module(add_extra_api=True) # type: ignore self.log_level = loglevel.value self.host = host self.port = port - self._env = env + self._env = env # type: ignore if env == Env.PROD: if self.workers == self.get_fields()["workers"].default: @@ -230,8 +241,12 @@ class UvicornBackendServer(CustomBackendServer): self.reload = True self.reload_dirs = [str(Path(get_config().app_name))] - def run_prod(self): - """Run in production mode.""" + def run_prod(self) -> list[str]: + """Run in production mode. + + Returns: + list[str]: Command ready to be executed + """ self.check_import() command = ["uvicorn"] @@ -255,7 +270,7 @@ class UvicornBackendServer(CustomBackendServer): if not self.is_default_value(key, value) } - self._app = Server( + self._app = Server( # type: ignore config=Config(**options_, app=self._app_uri), ) self._app.run() @@ -268,4 +283,3 @@ class UvicornBackendServer(CustomBackendServer): # TODO: hard because currently `*BackendServer` don't execute the server command, he just create it # if self._env == Env.PROD: # pass -