drop pydantic for dataclass & add uvicorn prod/dev
This commit is contained in:
parent
1076847c68
commit
7ecabafdd0
@ -697,28 +697,13 @@ class Config(Base):
|
||||
env_file: Optional[str] = None
|
||||
|
||||
# Custom Backend Server
|
||||
# backend_server_prod: server.CustomBackendServer = server.GunicornBackendServer(
|
||||
# worker_class="uvicorn.workers.UvicornH11Worker", # type: ignore
|
||||
# max_requests=100,
|
||||
# max_requests_jitter=25,
|
||||
# timeout=120,
|
||||
# )
|
||||
backend_server_prod: server.CustomBackendServer = server.GranianBackendServer(
|
||||
backend_server_prod: server.CustomBackendServer = server.GunicornBackendServer(
|
||||
threads=2,
|
||||
workers=4,
|
||||
)
|
||||
backend_server_dev: server.CustomBackendServer = server.GranianBackendServer(
|
||||
threads=1,
|
||||
backend_server_dev: server.CustomBackendServer = server.UvicornBackendServer(
|
||||
workers=1,
|
||||
)
|
||||
# backend_server_dev: server.CustomBackendServer = server.GunicornBackendServer(
|
||||
# worker_class="uvicorn.workers.UvicornH11Worker", # type: ignore
|
||||
# max_requests=100,
|
||||
# max_requests_jitter=25,
|
||||
# timeout=120,
|
||||
# threads=1,
|
||||
# workers=1,
|
||||
# )
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the config values.
|
||||
@ -730,7 +715,6 @@ class Config(Base):
|
||||
Raises:
|
||||
ConfigError: If some values in the config are invalid.
|
||||
"""
|
||||
print("[reflex.config::Config] start")
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Update the config from environment variables.
|
||||
|
@ -4,3 +4,10 @@ from .base import CustomBackendServer
|
||||
from .granian import GranianBackendServer
|
||||
from .gunicorn import GunicornBackendServer
|
||||
from .uvicorn import UvicornBackendServer
|
||||
|
||||
__all__ = [
|
||||
"CustomBackendServer",
|
||||
"GranianBackendServer",
|
||||
"GunicornBackendServer",
|
||||
"UvicornBackendServer",
|
||||
]
|
||||
|
@ -4,16 +4,160 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
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
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.constants.base import Env, LogLevel
|
||||
|
||||
ReturnCliTypeFn = Callable[[Any], str]
|
||||
|
||||
class CustomBackendServer(Base):
|
||||
|
||||
class CliType:
|
||||
"""Cli type transformer."""
|
||||
|
||||
@staticmethod
|
||||
def default(fmt: str) -> ReturnCliTypeFn:
|
||||
"""Default cli transformer.
|
||||
|
||||
Example:
|
||||
fmt: `'--env-file {value}'`
|
||||
value: `'/config.conf'`
|
||||
result => `'--env-file /config.conf'`
|
||||
"""
|
||||
|
||||
def wrapper(value: bool) -> str:
|
||||
return fmt.format(value=value)
|
||||
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
def boolean(fmt: str, bool_value: bool = True) -> ReturnCliTypeFn:
|
||||
"""When cli mode args only show when we want to activate it.
|
||||
|
||||
Example:
|
||||
fmt: `'--reload'`
|
||||
value: `False`
|
||||
result => `''`
|
||||
|
||||
Example:
|
||||
fmt: `'--reload'`
|
||||
value: `True`
|
||||
result => `'--reload'`
|
||||
"""
|
||||
|
||||
def wrapper(value: bool) -> str:
|
||||
return fmt if value is bool_value else ""
|
||||
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
def boolean_toggle(
|
||||
fmt: str,
|
||||
toggle_kw: str = "no",
|
||||
toggle_sep: str = "-",
|
||||
toggle_value: bool = False,
|
||||
**kwargs,
|
||||
) -> ReturnCliTypeFn:
|
||||
"""When the cli mode is a boolean toggle `--access-log`/`--no-access-log`.
|
||||
|
||||
Example:
|
||||
fmt: `'--{toggle_kw}{toggle_sep}access-log'`
|
||||
value: `False`
|
||||
toggle_value: `False` (default)
|
||||
result => `'--no-access-log'`
|
||||
|
||||
Example:
|
||||
fmt: `'--{toggle_kw}{toggle_sep}access-log'`
|
||||
value: `True`
|
||||
toggle_value: `False` (default)
|
||||
result => `'--access-log'`
|
||||
|
||||
Example:
|
||||
fmt: `'--{toggle_kw}{toggle_sep}access-log'`
|
||||
value: `True`
|
||||
toggle_value: `True`
|
||||
result => `'--no-access-log'`
|
||||
"""
|
||||
|
||||
def wrapper(value: bool) -> str:
|
||||
return fmt.format(
|
||||
**kwargs,
|
||||
toggle_kw=(toggle_kw if value is toggle_value else ""),
|
||||
toggle_sep=(toggle_sep if value is toggle_value else ""),
|
||||
)
|
||||
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
def multiple(
|
||||
fmt: str,
|
||||
join_sep: str | None = None,
|
||||
value_transformer: Callable[[Any], str] = lambda value: str(value),
|
||||
) -> ReturnCliTypeFn:
|
||||
r"""When the cli mode need multiple args or single args from an sequence.
|
||||
|
||||
Example (Multiple args mode):
|
||||
fmt: `'--header {value}'`.
|
||||
data_list: `['X-Forwarded-Proto=https', '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):
|
||||
fmt: `--headers {values}`
|
||||
data_list: `['X-Forwarded-Proto=https', 'X-Forwarded-For=0.0.0.0']`
|
||||
join_sep (required): `';'`
|
||||
result => `--headers \"X-Forwarded-Proto=https;X-Forwarded-For=0.0.0.0\"`
|
||||
|
||||
Example (Single args mode):
|
||||
fmt: `--headers {values}`
|
||||
data_list: `[('X-Forwarded-Proto', 'https'), ('X-Forwarded-For', '0.0.0.0')]`
|
||||
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\"`
|
||||
"""
|
||||
|
||||
def wrapper(values: Sequence[str]) -> str:
|
||||
return (
|
||||
fmt.format(
|
||||
values=join_sep.join(value_transformer(value) for value in values)
|
||||
)
|
||||
if join_sep
|
||||
else " ".join(
|
||||
[fmt.format(value=value_transformer(value)) for value in values]
|
||||
)
|
||||
)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def field_(
|
||||
*,
|
||||
default: Any = None,
|
||||
metadata_cli: ReturnCliTypeFn | None = None,
|
||||
exclude: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""Custom dataclass field builder."""
|
||||
params_ = {
|
||||
"default": default,
|
||||
"metadata": {"cli": metadata_cli, "exclude": exclude},
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
if kwargs.get("default_factory", False):
|
||||
params_.pop("default", None)
|
||||
|
||||
return dc_field(**params_)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomBackendServer:
|
||||
"""BackendServer base."""
|
||||
|
||||
_app_uri: str = field_(default="", metadata_cli=None, exclude=True)
|
||||
|
||||
@staticmethod
|
||||
def get_app_module(for_granian_target: bool = False, add_extra_api: bool = False):
|
||||
"""Get the app module for the backend.
|
||||
@ -74,8 +218,36 @@ class CustomBackendServer(Base):
|
||||
else need_threads
|
||||
)
|
||||
|
||||
def get_fields(self) -> dict[str, Field]:
|
||||
"""Return all the fields."""
|
||||
return self.__dataclass_fields__
|
||||
|
||||
def get_values(self) -> dict[str, Any]:
|
||||
"""Return all values."""
|
||||
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."""
|
||||
from dataclasses import MISSING
|
||||
|
||||
field = self.get_fields()[key]
|
||||
if value is None:
|
||||
value = getattr(self, key, None)
|
||||
|
||||
if field.default != MISSING:
|
||||
return value == field.default
|
||||
else:
|
||||
if field.default_factory != MISSING:
|
||||
return value == field.default_factory()
|
||||
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def check_import(self, extra: bool = False):
|
||||
def check_import(self):
|
||||
"""Check package importation."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -6,10 +6,10 @@ import sys
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import field as dc_field
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal, Type
|
||||
from typing import Any, Literal
|
||||
|
||||
from reflex.constants.base import Env, LogLevel
|
||||
from reflex.server.base import CustomBackendServer
|
||||
from reflex.server.base import CliType, CustomBackendServer, field_
|
||||
from reflex.utils import console
|
||||
|
||||
|
||||
@ -38,122 +38,160 @@ class HTTP2Settings: # https://github.com/emmett-framework/granian/blob/261ceba
|
||||
max_send_buffer_size: int = dc_field(default=1024 * 400)
|
||||
|
||||
|
||||
try:
|
||||
import watchfiles # type: ignore
|
||||
except ImportError:
|
||||
watchfiles = None
|
||||
|
||||
_mapping_attr_to_cli: dict[str, str] = {
|
||||
"address": "--host",
|
||||
"port": "--port",
|
||||
"interface": "--interface",
|
||||
"http": "--http",
|
||||
"websockets": "--ws", # NOTE: when `websockets` True: `--ws`; False: `--no-ws`
|
||||
"workers": "--workers",
|
||||
"threads": "--threads",
|
||||
"blocking_threads": "--blocking-threads",
|
||||
"threading_mode": "--threading-mode",
|
||||
"loop": "--loop",
|
||||
"loop_opt": "--opt", # NOTE: when `loop_opt` True: `--opt`; False: `--no-opt`
|
||||
"backlog": "--backlog",
|
||||
"backpressure": "--backpressure",
|
||||
"http1_keep_alive": "--http1-keep-alive",
|
||||
"http1_max_buffer_size": "--http1-max-buffer-size",
|
||||
"http1_pipeline_flush": "--http1-pipeline-flush",
|
||||
"http2_adaptive_window": "--http2-adaptive-window",
|
||||
"http2_initial_connection_window_size": "--http2-initial-connection-window-size",
|
||||
"http2_initial_stream_window_size": "--http2-initial-stream-window-size",
|
||||
"http2_keep_alive_interval": "--http2-keep-alive-interval",
|
||||
"http2_keep_alive_timeout": "--http2-keep-alive-timeout",
|
||||
"http2_max_concurrent_streams": "--http2-max-concurrent-streams",
|
||||
"http2_max_frame_size": "--http2-max-frame-size",
|
||||
"http2_max_headers_size": "--http2-max-headers-size",
|
||||
"http2_max_send_buffer_size": "--http2-max-send-buffer-size",
|
||||
"log_enabled": "--log", # NOTE: when `log_enabled` True: `--log`; False: `--no-log`
|
||||
"log_level": "--log-level",
|
||||
"log_access": "--log-access", # NOTE: when `log_access` True: `--log-access`; False: `--no-log-access`
|
||||
"log_access_format": "--access-log-fmt",
|
||||
"ssl_cert": "--ssl-certificate",
|
||||
"ssl_key": "--ssl-keyfile",
|
||||
"ssl_key_password": "--ssl-keyfile-password",
|
||||
"url_path_prefix": "--url-path-prefix",
|
||||
"respawn_failed_workers": "--respawn-failed-workers", # NOTE: when `respawn_failed_workers` True: `--respawn-failed-workers`; False: `--no-respawn-failed-workers`
|
||||
"respawn_interval": "--respawn-interval",
|
||||
"workers_lifetime": "--workers-lifetime",
|
||||
"factory": "--factory", # NOTE: when `factory` True: `--factory`; False: `--no-factory`
|
||||
"reload": "--reload", # NOTE: when `reload` True: `--reload`; False: `--no-reload`
|
||||
"reload_paths": "--reload-paths",
|
||||
"reload_ignore_dirs": "--reload-ignore-dirs",
|
||||
"reload_ignore_patterns": "--reload-ignore-patterns",
|
||||
"reload_ignore_paths": "--reload-ignore-paths",
|
||||
"process_name": "--process-name",
|
||||
"pid_file": "--pid-file",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class GranianBackendServer(CustomBackendServer):
|
||||
"""Granian backendServer."""
|
||||
"""Granian backendServer.
|
||||
|
||||
# https://github.com/emmett-framework/granian/blob/fc11808ed177362fcd9359a455a733065ddbc505/granian/server.py#L69
|
||||
https://github.com/emmett-framework/granian/blob/fc11808ed177362fcd9359a455a733065ddbc505/granian/cli.py#L52 (until Granian has the proper documentation)
|
||||
|
||||
target: str | None = None
|
||||
address: str = "127.0.0.1"
|
||||
port: int = 8000
|
||||
interface: Literal["asgi", "asginl", "rsgi", "wsgi"] = "rsgi"
|
||||
workers: int = 0
|
||||
threads: int = 0
|
||||
blocking_threads: int | None = None
|
||||
threading_mode: Literal["runtime", "workers"] = "workers"
|
||||
loop: Literal["auto", "asyncio", "uvloop"] = "auto"
|
||||
loop_opt: bool = False
|
||||
http: Literal["auto", "1", "2"] = "auto"
|
||||
websockets: bool = True
|
||||
backlog: int = 1024
|
||||
backpressure: int | None = None
|
||||
"""
|
||||
|
||||
# http1_settings: HTTP1Settings | None = None
|
||||
# NOTE: child of http1_settings, needed only for cli mode
|
||||
http1_keep_alive: bool = HTTP1Settings.keep_alive
|
||||
http1_max_buffer_size: int = HTTP1Settings.max_buffer_size
|
||||
http1_pipeline_flush: bool = HTTP1Settings.pipeline_flush
|
||||
|
||||
# http2_settings: HTTP2Settings | None = None
|
||||
# NOTE: child of http2_settings, needed only for cli mode
|
||||
http2_adaptive_window: bool = HTTP2Settings.adaptive_window
|
||||
http2_initial_connection_window_size: int = (
|
||||
HTTP2Settings.initial_connection_window_size
|
||||
address: str = field_(
|
||||
default="127.0.0.1", metadata_cli=CliType.default("--host {value}")
|
||||
)
|
||||
port: int = field_(default=8000, metadata_cli=CliType.default("--port {value}"))
|
||||
interface: Literal["asgi", "asginl", "rsgi", "wsgi"] = field_(
|
||||
default="rsgi", metadata_cli=CliType.default("--interface {value}")
|
||||
)
|
||||
workers: int = field_(default=0, metadata_cli=CliType.default("--workers {value}"))
|
||||
threads: int = field_(default=0, metadata_cli=CliType.default("--threads {value}"))
|
||||
blocking_threads: int | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--blocking-threads {value}")
|
||||
)
|
||||
threading_mode: Literal["runtime", "workers"] = field_(
|
||||
default="workers", metadata_cli=CliType.default("--threading-mode {value}")
|
||||
)
|
||||
loop: Literal["auto", "asyncio", "uvloop"] = field_(
|
||||
default="auto", metadata_cli=CliType.default("--loop {value}")
|
||||
)
|
||||
loop_opt: bool = field_(
|
||||
default=False,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}opt"),
|
||||
)
|
||||
http: Literal["auto", "1", "2"] = field_(
|
||||
default="auto", metadata_cli=CliType.default("--http {value}")
|
||||
)
|
||||
websockets: bool = field_(
|
||||
default=True, metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}ws")
|
||||
)
|
||||
backlog: int = field_(
|
||||
default=1024, metadata_cli=CliType.default("--backlog {value}")
|
||||
)
|
||||
backpressure: int | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--backpressure {value}")
|
||||
)
|
||||
http1_keep_alive: bool = field_(
|
||||
default=True, metadata_cli=CliType.default("--http1-keep-alive {value}")
|
||||
)
|
||||
http1_max_buffer_size: int = field_(
|
||||
default=417792, metadata_cli=CliType.default("--http1-max-buffer-size {value}")
|
||||
)
|
||||
http1_pipeline_flush: bool = field_(
|
||||
default=False, metadata_cli=CliType.default("--http1-pipeline-flush {value}")
|
||||
)
|
||||
http2_adaptive_window: bool = field_(
|
||||
default=False, metadata_cli=CliType.default("--http2-adaptive-window {value}")
|
||||
)
|
||||
http2_initial_connection_window_size: int = field_(
|
||||
default=1048576,
|
||||
metadata_cli=CliType.default("--http2-initial-connection-window-size {value}"),
|
||||
)
|
||||
http2_initial_stream_window_size: int = field_(
|
||||
default=1048576,
|
||||
metadata_cli=CliType.default("--http2-initial-stream-window-size {value}"),
|
||||
)
|
||||
http2_keep_alive_interval: int | None = field_(
|
||||
default=None,
|
||||
metadata_cli=CliType.default("--http2-keep-alive-interval {value}"),
|
||||
)
|
||||
http2_keep_alive_timeout: int = field_(
|
||||
default=20, metadata_cli=CliType.default("--http2-keep-alive-timeout {value}")
|
||||
)
|
||||
http2_max_concurrent_streams: int = field_(
|
||||
default=200,
|
||||
metadata_cli=CliType.default("--http2-max-concurrent-streams {value}"),
|
||||
)
|
||||
http2_max_frame_size: int = field_(
|
||||
default=16384, metadata_cli=CliType.default("--http2-max-frame-size {value}")
|
||||
)
|
||||
http2_max_headers_size: int = field_(
|
||||
default=16777216,
|
||||
metadata_cli=CliType.default("--http2-max-headers-size {value}"),
|
||||
)
|
||||
http2_max_send_buffer_size: int = field_(
|
||||
default=409600,
|
||||
metadata_cli=CliType.default("--http2-max-send-buffer-size {value}"),
|
||||
)
|
||||
log_enabled: bool = field_(
|
||||
default=True,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}log"),
|
||||
)
|
||||
log_level: Literal["critical", "error", "warning", "warn", "info", "debug"] = (
|
||||
field_(default="info", metadata_cli=CliType.default("--log-level {value}"))
|
||||
)
|
||||
log_dictconfig: dict[str, Any] | None = field_(default=None, metadata_cli=None)
|
||||
log_access: bool = field_(
|
||||
default=False,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}log-access"),
|
||||
)
|
||||
log_access_format: str | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--access-log-fmt {value}")
|
||||
)
|
||||
ssl_cert: Path | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--ssl-certificate {value}")
|
||||
)
|
||||
ssl_key: Path | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--ssl-keyfile {value}")
|
||||
)
|
||||
ssl_key_password: str | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--ssl-keyfile-password {value}")
|
||||
)
|
||||
url_path_prefix: str | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--url-path-prefix {value}")
|
||||
)
|
||||
respawn_failed_workers: bool = field_(
|
||||
default=False,
|
||||
metadata_cli=CliType.boolean_toggle(
|
||||
"--{toggle_kw}{toggle_sep}respawn-failed-workers"
|
||||
),
|
||||
)
|
||||
respawn_interval: float = field_(
|
||||
default=3.5, metadata_cli=CliType.default("--respawn-interval {value}")
|
||||
)
|
||||
workers_lifetime: int | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--workers-lifetime {value}")
|
||||
)
|
||||
factory: bool = field_(
|
||||
default=False,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}factory"),
|
||||
)
|
||||
reload: bool = field_(
|
||||
default=False,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}reload"),
|
||||
)
|
||||
reload_paths: list[Path] | None = field_(
|
||||
default=None, metadata_cli=CliType.multiple("--reload-paths {value}")
|
||||
)
|
||||
reload_ignore_dirs: list[str] | None = field_(
|
||||
default=None, metadata_cli=CliType.multiple("--reload-ignore-dirs {value}")
|
||||
)
|
||||
reload_ignore_patterns: list[str] | None = field_(
|
||||
default=None, metadata_cli=CliType.multiple("--reload-ignore-patterns {value}")
|
||||
)
|
||||
reload_ignore_paths: list[Path] | None = field_(
|
||||
default=None, metadata_cli=CliType.multiple("--reload-ignore-paths {value}")
|
||||
)
|
||||
reload_filter: object | None = field_( # type: ignore
|
||||
default=None, metadata_cli=None
|
||||
)
|
||||
process_name: str | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--process-name {value}")
|
||||
)
|
||||
pid_file: Path | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--pid-file {value}")
|
||||
)
|
||||
http2_initial_stream_window_size: int = HTTP2Settings.initial_stream_window_size
|
||||
http2_keep_alive_interval: int | None = HTTP2Settings.keep_alive_interval
|
||||
http2_keep_alive_timeout: int = HTTP2Settings.keep_alive_timeout
|
||||
http2_max_concurrent_streams: int = HTTP2Settings.max_concurrent_streams
|
||||
http2_max_frame_size: int = HTTP2Settings.max_frame_size
|
||||
http2_max_headers_size: int = HTTP2Settings.max_headers_size
|
||||
http2_max_send_buffer_size: int = HTTP2Settings.max_send_buffer_size
|
||||
|
||||
log_enabled: bool = True
|
||||
log_level: Literal["critical", "error", "warning", "warn", "info", "debug"] = "info"
|
||||
log_dictconfig: dict[str, Any] | None = None
|
||||
log_access: bool = False
|
||||
log_access_format: str | None = None
|
||||
ssl_cert: Path | None = None
|
||||
ssl_key: Path | None = None
|
||||
ssl_key_password: str | None = None
|
||||
url_path_prefix: str | None = None
|
||||
respawn_failed_workers: bool = False
|
||||
respawn_interval: float = 3.5
|
||||
workers_lifetime: int | None = None
|
||||
factory: bool = False
|
||||
reload: bool = False
|
||||
reload_paths: list[Path] | None = None
|
||||
reload_ignore_dirs: list[str] | None = None
|
||||
reload_ignore_patterns: list[str] | None = None
|
||||
reload_ignore_paths: list[Path] | None = None
|
||||
reload_filter: Type[getattr(watchfiles, "BaseFilter", None)] | None = None # type: ignore
|
||||
process_name: str | None = None
|
||||
pid_file: Path | None = None
|
||||
|
||||
def check_import(self, extra: bool = False):
|
||||
def check_import(self):
|
||||
"""Check package importation."""
|
||||
from importlib.util import find_spec
|
||||
|
||||
@ -164,11 +202,10 @@ class GranianBackendServer(CustomBackendServer):
|
||||
'The `granian` package is required to run `GranianBackendServer`. Run `pip install "granian>=1.6.0"`.'
|
||||
)
|
||||
|
||||
if find_spec("watchfiles") is None and extra:
|
||||
# NOTE: the `\[` is for force `rich.Console` to not consider it like a color or anything else which he not printing `[.*]`
|
||||
if find_spec("watchfiles") is None and self.reload:
|
||||
errors.append(
|
||||
r'Using --reload in `GranianBackendServer` requires the granian\[reload] extra. Run `pip install "granian\[reload]>=1.6.0"`.'
|
||||
) # type: ignore
|
||||
'Using `--reload` in `GranianBackendServer` requires the `watchfiles` extra. Run `pip install "watchfiles~=0.21"`.'
|
||||
)
|
||||
|
||||
if errors:
|
||||
console.error("\n".join(errors))
|
||||
@ -176,18 +213,18 @@ class GranianBackendServer(CustomBackendServer):
|
||||
|
||||
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||
"""Setup."""
|
||||
self.target = self.get_app_module(for_granian_target=True, add_extra_api=True)
|
||||
self._app_uri = self.get_app_module(for_granian_target=True, add_extra_api=True)
|
||||
self.log_level = loglevel.value # type: ignore
|
||||
self.address = host
|
||||
self.port = port
|
||||
self.interface = "asgi" # prevent obvious error
|
||||
self.interface = "asgi" # NOTE: prevent obvious error
|
||||
|
||||
if env == Env.PROD:
|
||||
if self.workers == self.get_fields()["workers"].default:
|
||||
self.workers = self.get_recommended_workers()
|
||||
else:
|
||||
if self.workers > (max_threads := self.get_max_workers()):
|
||||
self.workers = max_threads
|
||||
if self.workers > (max_workers := self.get_max_workers()):
|
||||
self.workers = max_workers
|
||||
|
||||
if self.threads == self.get_fields()["threads"].default:
|
||||
self.threads = self.get_recommended_threads()
|
||||
@ -208,26 +245,18 @@ class GranianBackendServer(CustomBackendServer):
|
||||
command = ["granian"]
|
||||
|
||||
for key, field in self.get_fields().items():
|
||||
if key != "target":
|
||||
value = getattr(self, key)
|
||||
if _mapping_attr_to_cli.get(key) and value != field.default:
|
||||
if isinstance(value, list):
|
||||
for v in value:
|
||||
command += [_mapping_attr_to_cli[key], str(v)]
|
||||
elif isinstance(value, bool):
|
||||
command.append(
|
||||
f"--{'no-' if value is False else ''}{_mapping_attr_to_cli[key][2:]}"
|
||||
)
|
||||
else:
|
||||
command += [_mapping_attr_to_cli[key], str(value)]
|
||||
if (
|
||||
field.metadata["exclude"] is False
|
||||
and field.metadata["cli"]
|
||||
and not self.is_default_value(key, (value := getattr(self, key)))
|
||||
):
|
||||
command += field.metadata["cli"](value).split(" ")
|
||||
|
||||
return command + [
|
||||
self.get_app_module(for_granian_target=True, add_extra_api=True)
|
||||
]
|
||||
return command + [self._app_uri]
|
||||
|
||||
def run_dev(self):
|
||||
"""Run in development mode."""
|
||||
self.check_import(extra=self.reload)
|
||||
self.check_import()
|
||||
from granian import Granian
|
||||
|
||||
exclude_keys = (
|
||||
@ -244,14 +273,17 @@ class GranianBackendServer(CustomBackendServer):
|
||||
"http2_max_headers_size",
|
||||
"http2_max_send_buffer_size",
|
||||
)
|
||||
model = self.get_fields()
|
||||
Granian(
|
||||
**{
|
||||
**{
|
||||
key: value
|
||||
for key, value in self.dict().items()
|
||||
if key not in exclude_keys and value != model[key].default
|
||||
for key, value in self.get_values().items()
|
||||
if (
|
||||
key not in exclude_keys
|
||||
and not self.is_default_value(key, value)
|
||||
)
|
||||
},
|
||||
"target": self._app_uri,
|
||||
"http1_settings": HTTP1Settings(
|
||||
self.http1_keep_alive,
|
||||
self.http1_max_buffer_size,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,18 +2,188 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# `UvicornBackendServer` defer from other `*BackendServer`, because `uvicorn` he is natively integrated inside the reflex project via Fastapi (same for asyncio)
|
||||
import asyncio
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
from configparser import RawConfigParser
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import IO, Any, Awaitable, Callable
|
||||
|
||||
from uvicorn import Config, Server
|
||||
from uvicorn.config import (
|
||||
LOGGING_CONFIG,
|
||||
SSL_PROTOCOL_VERSION,
|
||||
HTTPProtocolType,
|
||||
InterfaceType,
|
||||
LifespanType,
|
||||
LoopSetupType,
|
||||
WSProtocolType,
|
||||
)
|
||||
|
||||
from reflex.constants.base import Env, LogLevel
|
||||
from reflex.server.base import CustomBackendServer
|
||||
from reflex.server.base import CliType, CustomBackendServer, field_
|
||||
from reflex.utils import console
|
||||
|
||||
|
||||
# TODO
|
||||
@dataclass
|
||||
class UvicornBackendServer(CustomBackendServer):
|
||||
"""Uvicorn backendServer."""
|
||||
"""Uvicorn backendServer.
|
||||
|
||||
def check_import(self, extra: bool = False):
|
||||
https://www.uvicorn.org/settings/
|
||||
"""
|
||||
|
||||
host: str = field_(
|
||||
default="127.0.0.1", metadata_cli=CliType.default("--host {value}")
|
||||
)
|
||||
port: int = field_(default=8000, metadata_cli=CliType.default("--port {value}"))
|
||||
uds: str | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--uds {value}")
|
||||
)
|
||||
fd: int | None = field_(default=None, metadata_cli=CliType.default("--fd {value}"))
|
||||
loop: LoopSetupType = field_(
|
||||
default="auto", metadata_cli=CliType.default("--loop {value}")
|
||||
)
|
||||
http: type[asyncio.Protocol] | HTTPProtocolType = field_(
|
||||
default="auto", metadata_cli=CliType.default("--http {value}")
|
||||
)
|
||||
ws: type[asyncio.Protocol] | WSProtocolType = field_(
|
||||
default="auto", metadata_cli=CliType.default("--ws {value}")
|
||||
)
|
||||
ws_max_size: int = field_(
|
||||
default=16777216, metadata_cli=CliType.default("--ws-max-size {value}")
|
||||
)
|
||||
ws_max_queue: int = field_(
|
||||
default=32, metadata_cli=CliType.default("--ws-max-queue {value}")
|
||||
)
|
||||
ws_ping_interval: float | None = field_(
|
||||
default=20.0, metadata_cli=CliType.default("--ws-ping-interval {value}")
|
||||
)
|
||||
ws_ping_timeout: float | None = field_(
|
||||
default=20.0, metadata_cli=CliType.default("--ws-ping-timeout {value}")
|
||||
)
|
||||
ws_per_message_deflate: bool = field_(
|
||||
default=True, metadata_cli=CliType.default("--ws-per-message-deflate {value}")
|
||||
)
|
||||
lifespan: LifespanType = field_(
|
||||
default="auto", metadata_cli=CliType.default("--lifespan {value}")
|
||||
)
|
||||
env_file: str | os.PathLike[str] | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--env-file {value}")
|
||||
)
|
||||
log_config: dict[str, Any] | str | RawConfigParser | IO[Any] | None = field_(
|
||||
default=None,
|
||||
default_factory=lambda: LOGGING_CONFIG,
|
||||
metadata_cli=CliType.default("--log-config {value}"),
|
||||
)
|
||||
log_level: str | int | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--log-level {value}")
|
||||
)
|
||||
access_log: bool = field_(
|
||||
default=True,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}access-log"),
|
||||
)
|
||||
use_colors: bool | None = field_(
|
||||
default=None,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}use-colors"),
|
||||
)
|
||||
interface: InterfaceType = field_(
|
||||
default="auto", metadata_cli=CliType.default("--interface {value}")
|
||||
)
|
||||
reload: bool = field_(
|
||||
default=False, metadata_cli=CliType.default("--reload {value}")
|
||||
)
|
||||
reload_dirs: list[str] | str | None = field_(
|
||||
default=None, metadata_cli=CliType.multiple("--reload_dir {value}")
|
||||
)
|
||||
reload_delay: float = field_(
|
||||
default=0.25, metadata_cli=CliType.default("--reload-delay {value}")
|
||||
)
|
||||
reload_includes: list[str] | str | None = field_(
|
||||
default=None, metadata_cli=CliType.multiple("----reload-include {value}")
|
||||
)
|
||||
reload_excludes: list[str] | str | None = field_(
|
||||
default=None, metadata_cli=CliType.multiple("--reload-exclude {value}")
|
||||
)
|
||||
workers: int = field_(default=0, metadata_cli=CliType.default("--workers {value}"))
|
||||
proxy_headers: bool = field_(
|
||||
default=True,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}proxy-headers"),
|
||||
)
|
||||
server_header: bool = field_(
|
||||
default=True,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}server-header"),
|
||||
)
|
||||
date_header: bool = field_(
|
||||
default=True,
|
||||
metadata_cli=CliType.boolean_toggle("--{toggle_kw}{toggle_sep}date-header"),
|
||||
)
|
||||
forwarded_allow_ips: list[str] | str | None = field_(
|
||||
default=None,
|
||||
metadata_cli=CliType.multiple("--forwarded-allow-ips {value}", join_sep=","),
|
||||
)
|
||||
root_path: str = field_(
|
||||
default="", metadata_cli=CliType.default("--root-path {value}")
|
||||
)
|
||||
limit_concurrency: int | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--limit-concurrency {value}")
|
||||
)
|
||||
limit_max_requests: int | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--limit-max-requests {value}")
|
||||
)
|
||||
backlog: int = field_(
|
||||
default=2048, metadata_cli=CliType.default("--backlog {value}")
|
||||
)
|
||||
timeout_keep_alive: int = field_(
|
||||
default=5, metadata_cli=CliType.default("--timeout-keep-alive {value}")
|
||||
)
|
||||
timeout_notify: int = field_(default=30, metadata_cli=None)
|
||||
timeout_graceful_shutdown: int | None = field_(
|
||||
default=None,
|
||||
metadata_cli=CliType.default("--timeout-graceful-shutdown {value}"),
|
||||
)
|
||||
callback_notify: Callable[..., Awaitable[None]] | None = field_(
|
||||
default=None, metadata_cli=None
|
||||
)
|
||||
ssl_keyfile: str | os.PathLike[str] | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--ssl-keyfile {value}")
|
||||
)
|
||||
ssl_certfile: str | os.PathLike[str] | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--ssl-certfile {value}")
|
||||
)
|
||||
ssl_keyfile_password: str | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--ssl-keyfile-password {value}")
|
||||
)
|
||||
ssl_version: int = field_(
|
||||
default=SSL_PROTOCOL_VERSION,
|
||||
metadata_cli=CliType.default("--ssl-version {value}"),
|
||||
)
|
||||
ssl_cert_reqs: int = field_(
|
||||
default=ssl.CERT_NONE, metadata_cli=CliType.default("--ssl-cert-reqs {value}")
|
||||
)
|
||||
ssl_ca_certs: str | None = field_(
|
||||
default=None, metadata_cli=CliType.default("--ssl-ca-certs {value}")
|
||||
)
|
||||
ssl_ciphers: str = field_(
|
||||
default="TLSv1", metadata_cli=CliType.default("--ssl-ciphers {value}")
|
||||
)
|
||||
headers: list[tuple[str, str]] | None = field_(
|
||||
default=None,
|
||||
metadata_cli=CliType.multiple(
|
||||
"--header {value}", value_transformer=lambda value: f"{value[0]}:{value[1]}"
|
||||
),
|
||||
)
|
||||
factory: bool = field_(
|
||||
default=False, metadata_cli=CliType.default("--factory {value}")
|
||||
)
|
||||
h11_max_incomplete_event_size: int | None = field_(
|
||||
default=None,
|
||||
metadata_cli=CliType.default("--h11-max-incomplete-event-size {value}"),
|
||||
)
|
||||
|
||||
def check_import(self):
|
||||
"""Check package importation."""
|
||||
from importlib.util import find_spec
|
||||
|
||||
@ -24,18 +194,62 @@ class UvicornBackendServer(CustomBackendServer):
|
||||
'The `uvicorn` package is required to run `UvicornBackendServer`. Run `pip install "uvicorn>=0.20.0"`.'
|
||||
)
|
||||
|
||||
if find_spec("watchfiles") is None and (
|
||||
self.reload_includes and self.reload_excludes
|
||||
):
|
||||
errors.append(
|
||||
'Using `--reload-include` and `--reload-exclude` in `UvicornBackendServer` requires the `watchfiles` extra. Run `pip install "watchfiles>=0.13"`.'
|
||||
)
|
||||
|
||||
if errors:
|
||||
console.error("\n".join(errors))
|
||||
sys.exit()
|
||||
|
||||
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||
"""Setup."""
|
||||
pass
|
||||
self._app_uri = self.get_app_module(add_extra_api=True)
|
||||
self.log_level = loglevel.value
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
if env == Env.PROD:
|
||||
if self.workers == self.get_fields()["workers"].default:
|
||||
self.workers = self.get_recommended_workers()
|
||||
else:
|
||||
if self.workers > (max_workers := self.get_max_workers()):
|
||||
self.workers = max_workers
|
||||
|
||||
if env == Env.DEV:
|
||||
from reflex.config import get_config # prevent circular import
|
||||
|
||||
self.reload = True
|
||||
self.reload_dirs = [str(Path(get_config().app_name))]
|
||||
|
||||
def run_prod(self):
|
||||
"""Run in production mode."""
|
||||
pass
|
||||
self.check_import()
|
||||
command = ["uvicorn"]
|
||||
|
||||
for key, field in self.get_fields().items():
|
||||
if (
|
||||
field.metadata["exclude"] is False
|
||||
and field.metadata["cli"]
|
||||
and not self.is_default_value(key, (value := getattr(self, key)))
|
||||
):
|
||||
command += field.metadata["cli"](value).split(" ")
|
||||
|
||||
return command + [self._app_uri]
|
||||
|
||||
def run_dev(self):
|
||||
"""Run in development mode."""
|
||||
pass
|
||||
self.check_import()
|
||||
|
||||
options_ = {
|
||||
key: value
|
||||
for key, value in self.get_values().items()
|
||||
if not self.is_default_value(key, value)
|
||||
}
|
||||
|
||||
Server(
|
||||
config=Config(**options_, app=self._app_uri),
|
||||
).run()
|
||||
|
@ -203,9 +203,9 @@ def run_backend(
|
||||
notify_backend()
|
||||
|
||||
# Run the backend in development mode.
|
||||
backend_server_prod = config.backend_server_prod
|
||||
backend_server_prod.setup(host, port, loglevel, Env.DEV)
|
||||
backend_server_prod.run_dev()
|
||||
backend_server_dev = config.backend_server_dev
|
||||
backend_server_dev.setup(host, port, loglevel, Env.DEV)
|
||||
backend_server_dev.run_dev()
|
||||
|
||||
|
||||
def run_backend_prod(
|
||||
|
Loading…
Reference in New Issue
Block a user