lint & format

This commit is contained in:
KronosDev-Pro 2024-11-23 21:17:38 +00:00
parent f70c444833
commit 4a24b3640d
5 changed files with 216 additions and 53 deletions

View File

@ -698,11 +698,7 @@ class Config(Base):
# Custom Backend Server # Custom Backend Server
backend_server_prod: server.CustomBackendServer = server.GunicornBackendServer( backend_server_prod: server.CustomBackendServer = server.GunicornBackendServer(
threads=2, threads=2, workers=4, max_requests=100, max_requests_jitter=25, timeout=120
workers=4,
max_requests=100,
max_requests_jitter=25,
timeout=120
) )
backend_server_dev: server.CustomBackendServer = server.UvicornBackendServer( backend_server_dev: server.CustomBackendServer = server.UvicornBackendServer(
workers=1, workers=1,
@ -737,15 +733,21 @@ class Config(Base):
raise ConfigError( raise ConfigError(
"REDIS_URL is required when using the redis state manager." "REDIS_URL is required when using the redis state manager."
) )
if any( if any(
getattr(self.get_fields().get(key, None), "default", None) == self.get_value(key) getattr(self.get_fields().get(key, None), "default", None)
for key in ("timeout","gunicorn_worker_class", "gunicorn_workers", "gunicorn_max_requests", "gunicorn_max_requests_jitter" ) == self.get_value(key)
for key in (
"timeout",
"gunicorn_worker_class",
"gunicorn_workers",
"gunicorn_max_requests",
"gunicorn_max_requests_jitter",
)
): ):
console.warn( 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.' '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 @property
def module(self) -> str: def module(self) -> str:

View File

@ -7,7 +7,7 @@ from abc import abstractmethod
from dataclasses import Field, dataclass from dataclasses import Field, dataclass
from dataclasses import field as dc_field from dataclasses import field as dc_field
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Sequence, ClassVar from typing import Any, Callable, ClassVar, Sequence
from reflex import constants from reflex import constants
from reflex.constants.base import Env, LogLevel from reflex.constants.base import Env, LogLevel
@ -26,6 +26,12 @@ class CliType:
fmt: `'--env-file {value}'` fmt: `'--env-file {value}'`
value: `'/config.conf'` value: `'/config.conf'`
result => `'--env-file /config.conf'` result => `'--env-file /config.conf'`
Args:
fmt (str): format
Returns:
ReturnCliTypeFn: function wrapper
""" """
def wrapper(value: bool) -> str: def wrapper(value: bool) -> str:
@ -46,6 +52,13 @@ class CliType:
fmt: `'--reload'` fmt: `'--reload'`
value: `True` value: `True`
result => `'--reload'` result => `'--reload'`
Args:
fmt (str): format
bool_value (bool): boolean value used for toggle condition
Returns:
ReturnCliTypeFn: function wrapper
""" """
def wrapper(value: bool) -> str: def wrapper(value: bool) -> str:
@ -80,6 +93,16 @@ class CliType:
value: `True` value: `True`
toggle_value: `True` toggle_value: `True`
result => `'--no-access-log'` 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: def wrapper(value: bool) -> str:
@ -102,6 +125,7 @@ class CliType:
Example (Multiple args mode): Example (Multiple args mode):
fmt: `'--header {value}'`. fmt: `'--header {value}'`.
data_list: `['X-Forwarded-Proto=https', 'X-Forwarded-For=0.0.0.0']` 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\"'` result => `'--header \"X-Forwarded-Proto=https\" --header \"X-Forwarded-For=0.0.0.0\"'`
Example (Single args mode): Example (Single args mode):
@ -116,6 +140,14 @@ class CliType:
join_sep (required): `';'` join_sep (required): `';'`
value_transformer: `lambda value: f'{value[0]}:{value[1]}'` value_transformer: `lambda value: f'{value[0]}:{value[1]}'`
result => `--headers \"X-Forwarded-Proto:https;X-Forwarded-For:0.0.0.0\"` 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: def wrapper(values: Sequence[str]) -> str:
@ -139,7 +171,17 @@ def field_(
exclude: bool = False, exclude: bool = False,
**kwargs, **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_ = { params_ = {
"default": default, "default": default,
"metadata": {"cli": metadata_cli, "exclude": exclude}, "metadata": {"cli": metadata_cli, "exclude": exclude},
@ -156,16 +198,28 @@ def field_(
class CustomBackendServer: class CustomBackendServer:
"""BackendServer base.""" """BackendServer base."""
_env: ClassVar[Env] = field_(default=Env.DEV, metadata_cli=None, exclude=True, repr = False, init = False) _env: ClassVar[Env] = field_(
_app: ClassVar[Any] = field_(default=None, metadata_cli=None, exclude=True, repr = False, init = False) default=Env.DEV, metadata_cli=None, exclude=True, repr=False, init=False
_app_uri: ClassVar[str] = field_(default="", 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 @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. """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: Returns:
The app module for the backend. str: The app module for the backend.
""" """
import reflex import reflex
@ -177,21 +231,41 @@ class CustomBackendServer:
return f"{app_path}:{constants.CompileVars.APP}{f'.{constants.CompileVars.API}' if add_extra_api else ''}" return f"{app_path}:{constants.CompileVars.APP}{f'.{constants.CompileVars.API}' if add_extra_api else ''}"
def get_available_cpus(self) -> int: 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 return os.cpu_count() or 1
def get_max_workers(self) -> int: 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 # https://docs.gunicorn.org/en/latest/settings.html#workers
return (os.cpu_count() or 1) * 4 + 1 return (os.cpu_count() or 1) * 4 + 1
def get_recommended_workers(self) -> int: 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 # https://docs.gunicorn.org/en/latest/settings.html#workers
return (os.cpu_count() or 1) * 2 + 1 return (os.cpu_count() or 1) * 2 + 1
def get_max_threads(self, wait_time_ms: int = 50, service_time_ms: int = 5) -> int: 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 # https://engineering.zalando.com/posts/2019/04/how-to-set-an-ideal-thread-pool-size.html
# Brian Goetz formula # Brian Goetz formula
return int(self.get_available_cpus() * (1 + wait_time_ms / service_time_ms)) return int(self.get_available_cpus() * (1 + wait_time_ms / service_time_ms))
@ -202,7 +276,16 @@ class CustomBackendServer:
wait_time_ms: int = 50, wait_time_ms: int = 50,
service_time_ms: int = 5, service_time_ms: int = 5,
) -> int: ) -> 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 # https://engineering.zalando.com/posts/2019/04/how-to-set-an-ideal-thread-pool-size.html
max_available_threads = self.get_max_threads() max_available_threads = self.get_max_threads()
@ -221,19 +304,35 @@ class CustomBackendServer:
) )
def get_fields(self) -> dict[str, Field]: 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__ return self.__dataclass_fields__
def get_values(self) -> dict[str, Any]: def get_values(self) -> dict[str, Any]:
"""Return all values.""" """Return all values.
Returns:
dict[str, Any]: returns the value of the fields
"""
return { return {
key: getattr(self, key) key: getattr(self, key)
for key, field in self.__dataclass_fields__.items() for key, field in self.__dataclass_fields__.items()
if field.metadata["exclude"] is False if field.metadata["exclude"] is False
} }
def is_default_value(self, key, value: Any | None = None) -> bool: def is_default_value(self, key: str, value: Any | None = None) -> bool:
"""Check if the `value` is the same value from default context.""" """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 from dataclasses import MISSING
field = self.get_fields()[key] field = self.get_fields()[key]
@ -250,9 +349,13 @@ class CustomBackendServer:
@abstractmethod @abstractmethod
def get_backend_bind(self) -> tuple[str, int]: 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() raise NotImplementedError()
@abstractmethod @abstractmethod
def check_import(self): def check_import(self):
"""Check package importation.""" """Check package importation."""
@ -260,12 +363,23 @@ class CustomBackendServer:
@abstractmethod @abstractmethod
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env): 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() raise NotImplementedError()
@abstractmethod @abstractmethod
def run_prod(self): def run_prod(self) -> list[str]:
"""Run in production mode.""" """Run in production mode.
Returns:
list[str]: Command ready to be executed
"""
raise NotImplementedError() raise NotImplementedError()
@abstractmethod @abstractmethod

View File

@ -192,6 +192,11 @@ class GranianBackendServer(CustomBackendServer):
) )
def get_backend_bind(self) -> tuple[str, int]: 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 return self.address, self.port
def check_import(self): def check_import(self):
@ -215,13 +220,20 @@ class GranianBackendServer(CustomBackendServer):
sys.exit() sys.exit()
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env): def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
"""Setup.""" """Setup.
self._app_uri = self.get_app_module(for_granian_target=True, add_extra_api=True)
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.log_level = loglevel.value # type: ignore
self.address = host self.address = host
self.port = port self.port = port
self.interface = "asgi" # NOTE: prevent obvious error self.interface = "asgi" # NOTE: prevent obvious error
self._env = env self._env = env # type: ignore
if env == Env.PROD: if env == Env.PROD:
if self.workers == self.get_fields()["workers"].default: if self.workers == self.get_fields()["workers"].default:
@ -244,7 +256,11 @@ class GranianBackendServer(CustomBackendServer):
self.reload_ignore_dirs = [".web"] self.reload_ignore_dirs = [".web"]
def run_prod(self): def run_prod(self):
"""Run in production mode.""" """Run in production mode.
Returns:
list[str]: Command ready to be executed
"""
self.check_import() self.check_import()
command = ["granian"] command = ["granian"]
@ -261,7 +277,7 @@ class GranianBackendServer(CustomBackendServer):
def run_dev(self): def run_dev(self):
"""Run in development mode.""" """Run in development mode."""
self.check_import() self.check_import()
from granian import Granian from granian import Granian # type: ignore
exclude_keys = ( exclude_keys = (
"http1_keep_alive", "http1_keep_alive",
@ -277,7 +293,8 @@ class GranianBackendServer(CustomBackendServer):
"http2_max_headers_size", "http2_max_headers_size",
"http2_max_send_buffer_size", "http2_max_send_buffer_size",
) )
self._app = Granian(
self._app = Granian( # type: ignore
**{ **{
**{ **{
key: value key: value
@ -309,5 +326,6 @@ class GranianBackendServer(CustomBackendServer):
self._app.serve() self._app.serve()
async def shutdown(self): async def shutdown(self):
"""Shutdown the backend server."""
if self._app and self._env == Env.DEV: if self._app and self._env == Env.DEV:
self._app.shutdown() self._app.shutdown()

View File

@ -279,10 +279,14 @@ class GunicornBackendServer(CustomBackendServer):
) )
def get_backend_bind(self) -> tuple[str, int]: 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(":") host, port = self.bind[0].split(":")
return host, int(port) return host, int(port)
def check_import(self): def check_import(self):
"""Check package importation.""" """Check package importation."""
from importlib.util import find_spec from importlib.util import find_spec
@ -304,11 +308,18 @@ class GunicornBackendServer(CustomBackendServer):
sys.exit() sys.exit()
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env): def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
"""Setup.""" """Setup.
self._app_uri = f"{self.get_app_module()}()"
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.loglevel = loglevel.value # type: ignore
self.bind = [f"{host}:{port}"] self.bind = [f"{host}:{port}"]
self._env = env self._env = env # type: ignore
if env == Env.PROD: if env == Env.PROD:
if self.workers == self.get_fields()["workers"].default: if self.workers == self.get_fields()["workers"].default:
@ -328,7 +339,11 @@ class GunicornBackendServer(CustomBackendServer):
self.reload = True self.reload = True
def run_prod(self) -> list[str]: 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() self.check_import()
command = ["gunicorn"] command = ["gunicorn"]
@ -376,13 +391,13 @@ class GunicornBackendServer(CustomBackendServer):
def load(self): def load(self):
return gunicorn_import_app(self._app_uri) return gunicorn_import_app(self._app_uri)
def stop(self): def stop(self):
from gunicorn.arbiter import Arbiter from gunicorn.arbiter import Arbiter
Arbiter(self).stop() 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() self._app.run()
async def shutdown(self): async def shutdown(self):

View File

@ -184,9 +184,13 @@ class UvicornBackendServer(CustomBackendServer):
) )
def get_backend_bind(self) -> tuple[str, int]: 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 return self.host, self.port
def check_import(self): def check_import(self):
"""Check package importation.""" """Check package importation."""
from importlib.util import find_spec from importlib.util import find_spec
@ -210,12 +214,19 @@ class UvicornBackendServer(CustomBackendServer):
sys.exit() sys.exit()
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env): def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
"""Setup.""" """Setup.
self._app_uri = self.get_app_module(add_extra_api=True)
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.log_level = loglevel.value
self.host = host self.host = host
self.port = port self.port = port
self._env = env self._env = env # type: ignore
if env == Env.PROD: if env == Env.PROD:
if self.workers == self.get_fields()["workers"].default: if self.workers == self.get_fields()["workers"].default:
@ -230,8 +241,12 @@ class UvicornBackendServer(CustomBackendServer):
self.reload = True self.reload = True
self.reload_dirs = [str(Path(get_config().app_name))] self.reload_dirs = [str(Path(get_config().app_name))]
def run_prod(self): 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() self.check_import()
command = ["uvicorn"] command = ["uvicorn"]
@ -255,7 +270,7 @@ class UvicornBackendServer(CustomBackendServer):
if not self.is_default_value(key, value) if not self.is_default_value(key, value)
} }
self._app = Server( self._app = Server( # type: ignore
config=Config(**options_, app=self._app_uri), config=Config(**options_, app=self._app_uri),
) )
self._app.run() 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 # TODO: hard because currently `*BackendServer` don't execute the server command, he just create it
# if self._env == Env.PROD: # if self._env == Env.PROD:
# pass # pass