lint & format
This commit is contained in:
parent
f70c444833
commit
4a24b3640d
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user