Skip to content

Commit e417a99

Browse files
authored
move rx config away from pydantic (#5557)
* move rx config away from pydantic * handle cors_allowed_origins to be just a tuple
1 parent 8ef1638 commit e417a99

File tree

5 files changed

+104
-63
lines changed

5 files changed

+104
-63
lines changed

reflex/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,8 +517,8 @@ def _setup_state(self) -> None:
517517
async_mode="asgi",
518518
cors_allowed_origins=(
519519
"*"
520-
if config.cors_allowed_origins == ["*"]
521-
else config.cors_allowed_origins
520+
if config.cors_allowed_origins == ("*",)
521+
else list(config.cors_allowed_origins)
522522
),
523523
cors_credentials=True,
524524
max_http_buffer_size=environment.REFLEX_SOCKET_MAX_HTTP_BUFFER_SIZE.get(),

reflex/config.py

Lines changed: 96 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
"""The Reflex config."""
22

3-
from __future__ import annotations
4-
3+
import dataclasses
54
import importlib
65
import os
76
import sys
87
import threading
98
import urllib.parse
9+
from collections.abc import Sequence
1010
from importlib.util import find_spec
1111
from pathlib import Path
1212
from types import ModuleType
13-
from typing import Any, ClassVar
14-
15-
import pydantic.v1 as pydantic
13+
from typing import TYPE_CHECKING, Any, ClassVar
1614

1715
from reflex import constants
18-
from reflex.base import Base
1916
from reflex.constants.base import LogLevel
2017
from reflex.environment import EnvironmentVariables as EnvironmentVariables
2118
from reflex.environment import EnvVar as EnvVar
@@ -30,10 +27,10 @@
3027
from reflex.plugins import Plugin
3128
from reflex.utils import console
3229
from reflex.utils.exceptions import ConfigError
33-
from reflex.utils.types import true_type_for_pydantic_field
3430

3531

36-
class DBConfig(Base):
32+
@dataclasses.dataclass(kw_only=True)
33+
class DBConfig:
3734
"""Database config."""
3835

3936
engine: str
@@ -51,7 +48,7 @@ def postgresql(
5148
password: str | None = None,
5249
host: str | None = None,
5350
port: int | None = 5432,
54-
) -> DBConfig:
51+
) -> "DBConfig":
5552
"""Create an instance with postgresql engine.
5653
5754
Args:
@@ -81,7 +78,7 @@ def postgresql_psycopg(
8178
password: str | None = None,
8279
host: str | None = None,
8380
port: int | None = 5432,
84-
) -> DBConfig:
81+
) -> "DBConfig":
8582
"""Create an instance with postgresql+psycopg engine.
8683
8784
Args:
@@ -107,7 +104,7 @@ def postgresql_psycopg(
107104
def sqlite(
108105
cls,
109106
database: str,
110-
) -> DBConfig:
107+
) -> "DBConfig":
111108
"""Create an instance with sqlite engine.
112109
113110
Args:
@@ -145,32 +142,9 @@ def get_url(self) -> str:
145142
_sensitive_env_vars = {"DB_URL", "ASYNC_DB_URL", "REDIS_URL"}
146143

147144

148-
class Config(Base):
149-
"""The config defines runtime settings for the app.
150-
151-
By default, the config is defined in an `rxconfig.py` file in the root of the app.
152-
153-
```python
154-
# rxconfig.py
155-
import reflex as rx
156-
157-
config = rx.Config(
158-
app_name="myapp",
159-
api_url="http://localhost:8000",
160-
)
161-
```
162-
163-
Every config value can be overridden by an environment variable with the same name in uppercase.
164-
For example, `db_url` can be overridden by setting the `DB_URL` environment variable.
165-
166-
See the [configuration](https://reflex.dev/docs/getting-started/configuration/) docs for more info.
167-
"""
168-
169-
class Config: # pyright: ignore [reportIncompatibleVariableOverride]
170-
"""Pydantic config for the config."""
171-
172-
validate_assignment = True
173-
use_enum_values = False
145+
@dataclasses.dataclass(kw_only=True)
146+
class BaseConfig:
147+
"""Base config for the Reflex app."""
174148

175149
# The name of the app (should match the name of the app directory).
176150
app_name: str
@@ -218,13 +192,13 @@ class Config: # pyright: ignore [reportIncompatibleVariableOverride]
218192
static_page_generation_timeout: int = 60
219193

220194
# List of origins that are allowed to connect to the backend API.
221-
cors_allowed_origins: list[str] = ["*"]
195+
cors_allowed_origins: Sequence[str] = dataclasses.field(default=("*",))
222196

223197
# Whether to use React strict mode.
224198
react_strict_mode: bool = True
225199

226200
# Additional frontend packages to install.
227-
frontend_packages: list[str] = []
201+
frontend_packages: list[str] = dataclasses.field(default_factory=list)
228202

229203
# Indicate which type of state manager to use
230204
state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
@@ -239,7 +213,9 @@ class Config: # pyright: ignore [reportIncompatibleVariableOverride]
239213
redis_token_expiration: int = constants.Expiration.TOKEN
240214

241215
# Attributes that were explicitly set by the user.
242-
_non_default_attributes: set[str] = pydantic.PrivateAttr(set())
216+
_non_default_attributes: set[str] = dataclasses.field(
217+
default_factory=set, init=False
218+
)
243219

244220
# Path to file containing key-values pairs to override in the environment; Dotenv format.
245221
env_file: str | None = None
@@ -257,21 +233,50 @@ class Config: # pyright: ignore [reportIncompatibleVariableOverride]
257233
extra_overlay_function: str | None = None
258234

259235
# List of plugins to use in the app.
260-
plugins: list[Plugin] = []
236+
plugins: list[Plugin] = dataclasses.field(default_factory=list)
261237

262238
_prefixes: ClassVar[list[str]] = ["REFLEX_"]
263239

264-
def __init__(self, *args, **kwargs):
265-
"""Initialize the config values.
240+
241+
@dataclasses.dataclass(kw_only=True, init=False)
242+
class Config(BaseConfig):
243+
"""The config defines runtime settings for the app.
244+
245+
By default, the config is defined in an `rxconfig.py` file in the root of the app.
246+
247+
```python
248+
# rxconfig.py
249+
import reflex as rx
250+
251+
config = rx.Config(
252+
app_name="myapp",
253+
api_url="http://localhost:8000",
254+
)
255+
```
256+
257+
Every config value can be overridden by an environment variable with the same name in uppercase.
258+
For example, `db_url` can be overridden by setting the `DB_URL` environment variable.
259+
260+
See the [configuration](https://reflex.dev/docs/getting-started/configuration/) docs for more info.
261+
"""
262+
263+
def _post_init(self, **kwargs):
264+
"""Post-initialization method to set up the config.
265+
266+
This method is called after the config is initialized. It sets up the
267+
environment variables, updates the config from the environment, and
268+
replaces default URLs if ports were set.
266269
267270
Args:
268-
*args: The args to pass to the Pydantic init method.
269-
**kwargs: The kwargs to pass to the Pydantic init method.
271+
**kwargs: The kwargs passed to the Pydantic init method.
270272
271273
Raises:
272274
ConfigError: If some values in the config are invalid.
273275
"""
274-
super().__init__(*args, **kwargs)
276+
class_fields = self.class_fields()
277+
for key, value in kwargs.items():
278+
if key not in class_fields:
279+
setattr(self, key, value)
275280

276281
# Clean up this code when we remove plain envvar in 0.8.0
277282
env_loglevel = os.environ.get("REFLEX_LOGLEVEL")
@@ -287,7 +292,7 @@ def __init__(self, *args, **kwargs):
287292

288293
# Update default URLs if ports were set
289294
kwargs.update(env_kwargs)
290-
self._non_default_attributes.update(kwargs)
295+
self._non_default_attributes = set(kwargs.keys())
291296
self._replace_defaults(**kwargs)
292297

293298
if (
@@ -297,6 +302,41 @@ def __init__(self, *args, **kwargs):
297302
msg = f"{self._prefixes[0]}REDIS_URL is required when using the redis state manager."
298303
raise ConfigError(msg)
299304

305+
@classmethod
306+
def class_fields(cls) -> set[str]:
307+
"""Get the fields of the config class.
308+
309+
Returns:
310+
The fields of the config class.
311+
"""
312+
return {field.name for field in dataclasses.fields(cls)}
313+
314+
if not TYPE_CHECKING:
315+
316+
def __init__(self, **kwargs):
317+
"""Initialize the config values.
318+
319+
Args:
320+
**kwargs: The kwargs to pass to the Pydantic init method.
321+
322+
# noqa: DAR101 self
323+
"""
324+
class_fields = self.class_fields()
325+
super().__init__(**{k: v for k, v in kwargs.items() if k in class_fields})
326+
self._post_init(**kwargs)
327+
328+
def json(self) -> str:
329+
"""Get the config as a JSON string.
330+
331+
Returns:
332+
The config as a JSON string.
333+
"""
334+
import json
335+
336+
from reflex.utils.serializers import serialize
337+
338+
return json.dumps(self, default=serialize)
339+
300340
@property
301341
def app_module(self) -> ModuleType | None:
302342
"""Return the app module if `app_module_import` is set.
@@ -333,31 +373,33 @@ def update_from_env(self) -> dict[str, Any]:
333373

334374
updated_values = {}
335375
# Iterate over the fields.
336-
for key, field in self.__fields__.items():
376+
for field in dataclasses.fields(self):
337377
# The env var name is the key in uppercase.
338378
environment_variable = None
339379
for prefix in self._prefixes:
340-
if environment_variable := os.environ.get(f"{prefix}{key.upper()}"):
380+
if environment_variable := os.environ.get(
381+
f"{prefix}{field.name.upper()}"
382+
):
341383
break
342384

343385
# If the env var is set, override the config value.
344386
if environment_variable and environment_variable.strip():
345387
# Interpret the value.
346388
value = interpret_env_var_value(
347389
environment_variable,
348-
true_type_for_pydantic_field(field),
390+
field.type,
349391
field.name,
350392
)
351393

352394
# Set the value.
353-
updated_values[key] = value
395+
updated_values[field.name] = value
354396

355-
if key.upper() in _sensitive_env_vars:
397+
if field.name.upper() in _sensitive_env_vars:
356398
environment_variable = "***"
357399

358-
if value != getattr(self, key):
400+
if value != getattr(self, field.name):
359401
console.debug(
360-
f"Overriding config value {key} with env var {key.upper()}={environment_variable}",
402+
f"Overriding config value {field.name} with env var {field.name.upper()}={environment_variable}",
361403
dedupe=True,
362404
)
363405
return updated_values

reflex/environment.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import multiprocessing
1111
import os
1212
import platform
13-
from collections.abc import Callable
13+
from collections.abc import Callable, Sequence
1414
from functools import lru_cache
1515
from pathlib import Path
1616
from typing import (
@@ -227,7 +227,7 @@ def interpret_env_var_value(
227227
return interpret_existing_path_env(value, field_name)
228228
if field_type is Plugin:
229229
return interpret_plugin_env(value, field_name)
230-
if get_origin(field_type) is list:
230+
if get_origin(field_type) in (list, Sequence):
231231
return [
232232
interpret_env_var_value(
233233
v,

tests/integration/test_tailwind.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ def index():
5050
app.add_page(index)
5151
if not tailwind_version:
5252
config = rx.config.get_config()
53-
config.tailwind = None
5453
config.plugins = []
5554
elif tailwind_version == 3:
5655
config = rx.config.get_config()

tests/units/test_config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
def test_requires_app_name():
2323
"""Test that a config requires an app_name."""
24-
with pytest.raises(ValueError):
25-
rx.Config()
24+
with pytest.raises(TypeError):
25+
rx.Config() # pyright: ignore[reportCallIssue]
2626

2727

2828
def test_set_app_name(base_config_values):
@@ -170,7 +170,7 @@ def test_event_namespace(mocker: MockerFixture, kwargs, expected):
170170
(
171171
{"backend_port": 8001, "frontend_port": 3001},
172172
{"REFLEX_BACKEND_PORT": 8002},
173-
{"frontend_port": "3005"},
173+
{"frontend_port": 3005},
174174
{
175175
"api_url": "http://localhost:8002",
176176
"backend_port": 8002,
@@ -182,7 +182,7 @@ def test_event_namespace(mocker: MockerFixture, kwargs, expected):
182182
(
183183
{"api_url": "http://foo.bar:8900", "deploy_url": "http://foo.bar:3001"},
184184
{"REFLEX_BACKEND_PORT": 8002},
185-
{"frontend_port": "3005"},
185+
{"frontend_port": 3005},
186186
{
187187
"api_url": "http://foo.bar:8900",
188188
"backend_port": 8002,

0 commit comments

Comments
 (0)