11"""The Reflex config."""
22
3- from __future__ import annotations
4-
3+ import dataclasses
54import importlib
65import os
76import sys
87import threading
98import urllib .parse
9+ from collections .abc import Sequence
1010from importlib .util import find_spec
1111from pathlib import Path
1212from 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
1715from reflex import constants
18- from reflex .base import Base
1916from reflex .constants .base import LogLevel
2017from reflex .environment import EnvironmentVariables as EnvironmentVariables
2118from reflex .environment import EnvVar as EnvVar
3027from reflex .plugins import Plugin
3128from reflex .utils import console
3229from 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
0 commit comments