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
|
env_file: Optional[str] = None
|
||||||
|
|
||||||
# Custom Backend Server
|
# Custom Backend Server
|
||||||
# backend_server_prod: server.CustomBackendServer = server.GunicornBackendServer(
|
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(
|
|
||||||
threads=2,
|
threads=2,
|
||||||
workers=4,
|
workers=4,
|
||||||
)
|
)
|
||||||
backend_server_dev: server.CustomBackendServer = server.GranianBackendServer(
|
backend_server_dev: server.CustomBackendServer = server.UvicornBackendServer(
|
||||||
threads=1,
|
|
||||||
workers=1,
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize the config values.
|
"""Initialize the config values.
|
||||||
@ -730,7 +715,6 @@ class Config(Base):
|
|||||||
Raises:
|
Raises:
|
||||||
ConfigError: If some values in the config are invalid.
|
ConfigError: If some values in the config are invalid.
|
||||||
"""
|
"""
|
||||||
print("[reflex.config::Config] start")
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Update the config from environment variables.
|
# Update the config from environment variables.
|
||||||
|
@ -4,3 +4,10 @@ from .base import CustomBackendServer
|
|||||||
from .granian import GranianBackendServer
|
from .granian import GranianBackendServer
|
||||||
from .gunicorn import GunicornBackendServer
|
from .gunicorn import GunicornBackendServer
|
||||||
from .uvicorn import UvicornBackendServer
|
from .uvicorn import UvicornBackendServer
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"CustomBackendServer",
|
||||||
|
"GranianBackendServer",
|
||||||
|
"GunicornBackendServer",
|
||||||
|
"UvicornBackendServer",
|
||||||
|
]
|
||||||
|
@ -4,16 +4,160 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from dataclasses import Field, dataclass
|
||||||
|
from dataclasses import field as dc_field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, Callable, Sequence
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.base import Base
|
|
||||||
from reflex.constants.base import Env, LogLevel
|
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."""
|
"""BackendServer base."""
|
||||||
|
|
||||||
|
_app_uri: str = field_(default="", metadata_cli=None, exclude=True)
|
||||||
|
|
||||||
@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):
|
||||||
"""Get the app module for the backend.
|
"""Get the app module for the backend.
|
||||||
@ -74,8 +218,36 @@ class CustomBackendServer(Base):
|
|||||||
else need_threads
|
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
|
@abstractmethod
|
||||||
def check_import(self, extra: bool = False):
|
def check_import(self):
|
||||||
"""Check package importation."""
|
"""Check package importation."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ import sys
|
|||||||
from dataclasses import dataclass
|
from dataclasses import 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, Literal, Type
|
from typing import Any, Literal
|
||||||
|
|
||||||
from reflex.constants.base import Env, LogLevel
|
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
|
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)
|
max_send_buffer_size: int = dc_field(default=1024 * 400)
|
||||||
|
|
||||||
|
|
||||||
try:
|
@dataclass
|
||||||
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",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GranianBackendServer(CustomBackendServer):
|
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
|
address: str = field_(
|
||||||
# NOTE: child of http1_settings, needed only for cli mode
|
default="127.0.0.1", metadata_cli=CliType.default("--host {value}")
|
||||||
http1_keep_alive: bool = HTTP1Settings.keep_alive
|
)
|
||||||
http1_max_buffer_size: int = HTTP1Settings.max_buffer_size
|
port: int = field_(default=8000, metadata_cli=CliType.default("--port {value}"))
|
||||||
http1_pipeline_flush: bool = HTTP1Settings.pipeline_flush
|
interface: Literal["asgi", "asginl", "rsgi", "wsgi"] = field_(
|
||||||
|
default="rsgi", metadata_cli=CliType.default("--interface {value}")
|
||||||
# http2_settings: HTTP2Settings | None = None
|
)
|
||||||
# NOTE: child of http2_settings, needed only for cli mode
|
workers: int = field_(default=0, metadata_cli=CliType.default("--workers {value}"))
|
||||||
http2_adaptive_window: bool = HTTP2Settings.adaptive_window
|
threads: int = field_(default=0, metadata_cli=CliType.default("--threads {value}"))
|
||||||
http2_initial_connection_window_size: int = (
|
blocking_threads: int | None = field_(
|
||||||
HTTP2Settings.initial_connection_window_size
|
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
|
def check_import(self):
|
||||||
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):
|
|
||||||
"""Check package importation."""
|
"""Check package importation."""
|
||||||
from importlib.util import find_spec
|
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"`.'
|
'The `granian` package is required to run `GranianBackendServer`. Run `pip install "granian>=1.6.0"`.'
|
||||||
)
|
)
|
||||||
|
|
||||||
if find_spec("watchfiles") is None and extra:
|
if find_spec("watchfiles") is None and self.reload:
|
||||||
# NOTE: the `\[` is for force `rich.Console` to not consider it like a color or anything else which he not printing `[.*]`
|
|
||||||
errors.append(
|
errors.append(
|
||||||
r'Using --reload in `GranianBackendServer` requires the granian\[reload] extra. Run `pip install "granian\[reload]>=1.6.0"`.'
|
'Using `--reload` in `GranianBackendServer` requires the `watchfiles` extra. Run `pip install "watchfiles~=0.21"`.'
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
console.error("\n".join(errors))
|
console.error("\n".join(errors))
|
||||||
@ -176,18 +213,18 @@ class GranianBackendServer(CustomBackendServer):
|
|||||||
|
|
||||||
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.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.log_level = loglevel.value # type: ignore
|
||||||
self.address = host
|
self.address = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.interface = "asgi" # prevent obvious error
|
self.interface = "asgi" # NOTE: prevent obvious error
|
||||||
|
|
||||||
if env == Env.PROD:
|
if env == Env.PROD:
|
||||||
if self.workers == self.get_fields()["workers"].default:
|
if self.workers == self.get_fields()["workers"].default:
|
||||||
self.workers = self.get_recommended_workers()
|
self.workers = self.get_recommended_workers()
|
||||||
else:
|
else:
|
||||||
if self.workers > (max_threads := self.get_max_workers()):
|
if self.workers > (max_workers := self.get_max_workers()):
|
||||||
self.workers = max_threads
|
self.workers = max_workers
|
||||||
|
|
||||||
if self.threads == self.get_fields()["threads"].default:
|
if self.threads == self.get_fields()["threads"].default:
|
||||||
self.threads = self.get_recommended_threads()
|
self.threads = self.get_recommended_threads()
|
||||||
@ -208,26 +245,18 @@ class GranianBackendServer(CustomBackendServer):
|
|||||||
command = ["granian"]
|
command = ["granian"]
|
||||||
|
|
||||||
for key, field in self.get_fields().items():
|
for key, field in self.get_fields().items():
|
||||||
if key != "target":
|
if (
|
||||||
value = getattr(self, key)
|
field.metadata["exclude"] is False
|
||||||
if _mapping_attr_to_cli.get(key) and value != field.default:
|
and field.metadata["cli"]
|
||||||
if isinstance(value, list):
|
and not self.is_default_value(key, (value := getattr(self, key)))
|
||||||
for v in value:
|
):
|
||||||
command += [_mapping_attr_to_cli[key], str(v)]
|
command += field.metadata["cli"](value).split(" ")
|
||||||
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)]
|
|
||||||
|
|
||||||
return command + [
|
return command + [self._app_uri]
|
||||||
self.get_app_module(for_granian_target=True, add_extra_api=True)
|
|
||||||
]
|
|
||||||
|
|
||||||
def run_dev(self):
|
def run_dev(self):
|
||||||
"""Run in development mode."""
|
"""Run in development mode."""
|
||||||
self.check_import(extra=self.reload)
|
self.check_import()
|
||||||
from granian import Granian
|
from granian import Granian
|
||||||
|
|
||||||
exclude_keys = (
|
exclude_keys = (
|
||||||
@ -244,14 +273,17 @@ class GranianBackendServer(CustomBackendServer):
|
|||||||
"http2_max_headers_size",
|
"http2_max_headers_size",
|
||||||
"http2_max_send_buffer_size",
|
"http2_max_send_buffer_size",
|
||||||
)
|
)
|
||||||
model = self.get_fields()
|
|
||||||
Granian(
|
Granian(
|
||||||
**{
|
**{
|
||||||
**{
|
**{
|
||||||
key: value
|
key: value
|
||||||
for key, value in self.dict().items()
|
for key, value in self.get_values().items()
|
||||||
if key not in exclude_keys and value != model[key].default
|
if (
|
||||||
|
key not in exclude_keys
|
||||||
|
and not self.is_default_value(key, value)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
"target": self._app_uri,
|
||||||
"http1_settings": HTTP1Settings(
|
"http1_settings": HTTP1Settings(
|
||||||
self.http1_keep_alive,
|
self.http1_keep_alive,
|
||||||
self.http1_max_buffer_size,
|
self.http1_max_buffer_size,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,18 +2,188 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
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
|
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.constants.base import Env, LogLevel
|
||||||
from reflex.server.base import CustomBackendServer
|
from reflex.server.base import CliType, CustomBackendServer, field_
|
||||||
from reflex.utils import console
|
from reflex.utils import console
|
||||||
|
|
||||||
|
|
||||||
# TODO
|
@dataclass
|
||||||
class UvicornBackendServer(CustomBackendServer):
|
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."""
|
"""Check package importation."""
|
||||||
from importlib.util import find_spec
|
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"`.'
|
'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:
|
if errors:
|
||||||
console.error("\n".join(errors))
|
console.error("\n".join(errors))
|
||||||
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."""
|
||||||
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):
|
def run_prod(self):
|
||||||
"""Run in production mode."""
|
"""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):
|
def run_dev(self):
|
||||||
"""Run in development mode."""
|
"""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()
|
notify_backend()
|
||||||
|
|
||||||
# Run the backend in development mode.
|
# Run the backend in development mode.
|
||||||
backend_server_prod = config.backend_server_prod
|
backend_server_dev = config.backend_server_dev
|
||||||
backend_server_prod.setup(host, port, loglevel, Env.DEV)
|
backend_server_dev.setup(host, port, loglevel, Env.DEV)
|
||||||
backend_server_prod.run_dev()
|
backend_server_dev.run_dev()
|
||||||
|
|
||||||
|
|
||||||
def run_backend_prod(
|
def run_backend_prod(
|
||||||
|
Loading…
Reference in New Issue
Block a user