reflex 0.5.6a2__py3-none-any.whl → 0.5.7a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (39) hide show
  1. reflex/.templates/jinja/web/utils/context.js.jinja2 +2 -0
  2. reflex/.templates/web/utils/state.js +31 -2
  3. reflex/app.py +190 -4
  4. reflex/compiler/templates.py +1 -0
  5. reflex/components/base/__init__.py +4 -0
  6. reflex/components/base/__init__.pyi +2 -0
  7. reflex/components/base/error_boundary.py +78 -0
  8. reflex/components/base/error_boundary.pyi +98 -0
  9. reflex/components/radix/primitives/form.py +3 -2
  10. reflex/components/recharts/cartesian.py +31 -16
  11. reflex/components/recharts/cartesian.pyi +46 -22
  12. reflex/components/recharts/charts.py +6 -6
  13. reflex/components/recharts/charts.pyi +10 -10
  14. reflex/components/recharts/general.py +7 -0
  15. reflex/components/recharts/general.pyi +5 -0
  16. reflex/components/recharts/recharts.py +16 -1
  17. reflex/components/recharts/recharts.pyi +1 -0
  18. reflex/config.py +2 -1
  19. reflex/constants/compiler.py +20 -2
  20. reflex/constants/config.py +1 -1
  21. reflex/event.py +9 -0
  22. reflex/experimental/__init__.py +2 -0
  23. reflex/experimental/vars/__init__.py +3 -0
  24. reflex/experimental/vars/base.py +210 -0
  25. reflex/state.py +85 -18
  26. reflex/testing.py +28 -0
  27. reflex/utils/compat.py +7 -5
  28. reflex/utils/console.py +10 -1
  29. reflex/utils/prerequisites.py +11 -5
  30. reflex/utils/processes.py +7 -2
  31. reflex/utils/pyi_generator.py +4 -1
  32. reflex/utils/types.py +35 -30
  33. reflex/vars.py +58 -23
  34. reflex/vars.pyi +4 -1
  35. {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/METADATA +1 -1
  36. {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/RECORD +39 -35
  37. {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/LICENSE +0 -0
  38. {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/WHEEL +0 -0
  39. {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/entry_points.txt +0 -0
@@ -62,9 +62,17 @@ class CompileVars(SimpleNamespace):
62
62
  # The name of the function for converting a dict to an event.
63
63
  TO_EVENT = "Event"
64
64
  # The name of the internal on_load event.
65
- ON_LOAD_INTERNAL = "on_load_internal_state.on_load_internal"
65
+ ON_LOAD_INTERNAL = "reflex___state____on_load_internal_state.on_load_internal"
66
66
  # The name of the internal event to update generic state vars.
67
- UPDATE_VARS_INTERNAL = "update_vars_internal_state.update_vars_internal"
67
+ UPDATE_VARS_INTERNAL = (
68
+ "reflex___state____update_vars_internal_state.update_vars_internal"
69
+ )
70
+ # The name of the frontend event exception state
71
+ FRONTEND_EXCEPTION_STATE = "reflex___state____frontend_event_exception_state"
72
+ # The full name of the frontend exception state
73
+ FRONTEND_EXCEPTION_STATE_FULL = (
74
+ f"reflex___state____state.{FRONTEND_EXCEPTION_STATE}"
75
+ )
68
76
 
69
77
 
70
78
  class PageNames(SimpleNamespace):
@@ -124,6 +132,16 @@ class Hooks(SimpleNamespace):
124
132
  }
125
133
  })"""
126
134
 
135
+ FRONTEND_ERRORS = f"""
136
+ const logFrontendError = (error, info) => {{
137
+ if (process.env.NODE_ENV === "production") {{
138
+ addEvents([Event("{CompileVars.FRONTEND_EXCEPTION_STATE_FULL}.handle_frontend_exception", {{
139
+ stack: error.stack,
140
+ }})])
141
+ }}
142
+ }}
143
+ """
144
+
127
145
 
128
146
  class MemoizationDisposition(enum.Enum):
129
147
  """The conditions under which a component should be memoized."""
@@ -39,7 +39,7 @@ class GitIgnore(SimpleNamespace):
39
39
  # The gitignore file.
40
40
  FILE = ".gitignore"
41
41
  # Files to gitignore.
42
- DEFAULTS = {Dirs.WEB, "*.db", "__pycache__/", "*.py[cod]"}
42
+ DEFAULTS = {Dirs.WEB, "*.db", "__pycache__/", "*.py[cod]", "assets/external/"}
43
43
 
44
44
 
45
45
  class RequirementsTxt(SimpleNamespace):
reflex/event.py CHANGED
@@ -509,6 +509,15 @@ def console_log(message: str | Var[str]) -> EventSpec:
509
509
  return server_side("_console", get_fn_signature(console_log), message=message)
510
510
 
511
511
 
512
+ def back() -> EventSpec:
513
+ """Do a history.back on the browser.
514
+
515
+ Returns:
516
+ An event to go back one page.
517
+ """
518
+ return call_script("window.history.back()")
519
+
520
+
512
521
  def window_alert(message: str | Var[str]) -> EventSpec:
513
522
  """Create a window alert on the browser.
514
523
 
@@ -8,6 +8,7 @@ from reflex.components.sonner.toast import toast as toast
8
8
 
9
9
  from ..utils.console import warn
10
10
  from . import hooks as hooks
11
+ from . import vars as vars
11
12
  from .assets import asset as asset
12
13
  from .client_state import ClientStateVar as ClientStateVar
13
14
  from .layout import layout as layout
@@ -42,6 +43,7 @@ _x = ExperimentalNamespace(
42
43
  asset=asset,
43
44
  client_state=ClientStateVar.create,
44
45
  hooks=hooks,
46
+ vars=vars,
45
47
  layout=layout,
46
48
  progress=progress,
47
49
  PropsBase=PropsBase,
@@ -0,0 +1,3 @@
1
+ """Experimental Immutable-Based Var System."""
2
+
3
+ from .base import ImmutableVar as ImmutableVar
@@ -0,0 +1,210 @@
1
+ """Collection of base classes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import dataclasses
6
+ import sys
7
+ from typing import Any, Optional, Type
8
+
9
+ from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
10
+ from reflex.utils import serializers, types
11
+ from reflex.utils.exceptions import VarTypeError
12
+ from reflex.vars import Var, VarData, _decode_var, _extract_var_data, _global_vars
13
+
14
+
15
+ @dataclasses.dataclass(
16
+ eq=False,
17
+ frozen=True,
18
+ **{"slots": True} if sys.version_info >= (3, 10) else {},
19
+ )
20
+ class ImmutableVar(Var):
21
+ """Base class for immutable vars."""
22
+
23
+ # The name of the var.
24
+ _var_name: str = dataclasses.field()
25
+
26
+ # The type of the var.
27
+ _var_type: Type = dataclasses.field(default=Any)
28
+
29
+ # Extra metadata associated with the Var
30
+ _var_data: Optional[VarData] = dataclasses.field(default=None)
31
+
32
+ @property
33
+ def _var_is_local(self) -> bool:
34
+ """Whether this is a local javascript variable.
35
+
36
+ Returns:
37
+ False
38
+ """
39
+ return False
40
+
41
+ @property
42
+ def _var_is_string(self) -> bool:
43
+ """Whether the var is a string literal.
44
+
45
+ Returns:
46
+ False
47
+ """
48
+ return False
49
+
50
+ @property
51
+ def _var_full_name_needs_state_prefix(self) -> bool:
52
+ """Whether the full name of the var needs a _var_state prefix.
53
+
54
+ Returns:
55
+ False
56
+ """
57
+ return False
58
+
59
+ def __post_init__(self):
60
+ """Post-initialize the var."""
61
+ # Decode any inline Var markup and apply it to the instance
62
+ _var_data, _var_name = _decode_var(self._var_name)
63
+ if _var_data:
64
+ self.__init__(
65
+ _var_name, self._var_type, VarData.merge(self._var_data, _var_data)
66
+ )
67
+
68
+ def _replace(self, merge_var_data=None, **kwargs: Any):
69
+ """Make a copy of this Var with updated fields.
70
+
71
+ Args:
72
+ merge_var_data: VarData to merge into the existing VarData.
73
+ **kwargs: Var fields to update.
74
+
75
+ Returns:
76
+ A new ImmutableVar with the updated fields overwriting the corresponding fields in this Var.
77
+
78
+ Raises:
79
+ TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None.
80
+ """
81
+ if kwargs.get("_var_is_local", False) is not False:
82
+ raise TypeError(
83
+ "The _var_is_local argument is not supported for ImmutableVar."
84
+ )
85
+
86
+ if kwargs.get("_var_is_string", False) is not False:
87
+ raise TypeError(
88
+ "The _var_is_string argument is not supported for ImmutableVar."
89
+ )
90
+
91
+ if kwargs.get("_var_full_name_needs_state_prefix", False) is not False:
92
+ raise TypeError(
93
+ "The _var_full_name_needs_state_prefix argument is not supported for ImmutableVar."
94
+ )
95
+
96
+ field_values = dict(
97
+ _var_name=kwargs.pop("_var_name", self._var_name),
98
+ _var_type=kwargs.pop("_var_type", self._var_type),
99
+ _var_data=VarData.merge(
100
+ kwargs.get("_var_data", self._var_data), merge_var_data
101
+ ),
102
+ )
103
+ return ImmutableVar(**field_values)
104
+
105
+ @classmethod
106
+ def create(
107
+ cls,
108
+ value: Any,
109
+ _var_is_local: bool | None = None,
110
+ _var_is_string: bool | None = None,
111
+ _var_data: VarData | None = None,
112
+ ) -> Var | None:
113
+ """Create a var from a value.
114
+
115
+ Args:
116
+ value: The value to create the var from.
117
+ _var_is_local: Whether the var is local. Deprecated.
118
+ _var_is_string: Whether the var is a string literal. Deprecated.
119
+ _var_data: Additional hooks and imports associated with the Var.
120
+
121
+ Returns:
122
+ The var.
123
+
124
+ Raises:
125
+ VarTypeError: If the value is JSON-unserializable.
126
+ TypeError: If _var_is_local or _var_is_string is not None.
127
+ """
128
+ if _var_is_local is not None:
129
+ raise TypeError(
130
+ "The _var_is_local argument is not supported for ImmutableVar."
131
+ )
132
+
133
+ if _var_is_string is not None:
134
+ raise TypeError(
135
+ "The _var_is_string argument is not supported for ImmutableVar."
136
+ )
137
+
138
+ from reflex.utils import format
139
+
140
+ # Check for none values.
141
+ if value is None:
142
+ return None
143
+
144
+ # If the value is already a var, do nothing.
145
+ if isinstance(value, Var):
146
+ return value
147
+
148
+ # Try to pull the imports and hooks from contained values.
149
+ if not isinstance(value, str):
150
+ _var_data = VarData.merge(*_extract_var_data(value), _var_data)
151
+
152
+ # Try to serialize the value.
153
+ type_ = type(value)
154
+ if type_ in types.JSONType:
155
+ name = value
156
+ else:
157
+ name, _serialized_type = serializers.serialize(value, get_type=True)
158
+ if name is None:
159
+ raise VarTypeError(
160
+ f"No JSON serializer found for var {value} of type {type_}."
161
+ )
162
+ name = name if isinstance(name, str) else format.json_dumps(name)
163
+
164
+ return ImmutableVar(
165
+ _var_name=name,
166
+ _var_type=type_,
167
+ _var_data=_var_data,
168
+ )
169
+
170
+ @classmethod
171
+ def create_safe(
172
+ cls,
173
+ value: Any,
174
+ _var_is_local: bool | None = None,
175
+ _var_is_string: bool | None = None,
176
+ _var_data: VarData | None = None,
177
+ ) -> Var:
178
+ """Create a var from a value, asserting that it is not None.
179
+
180
+ Args:
181
+ value: The value to create the var from.
182
+ _var_is_local: Whether the var is local. Deprecated.
183
+ _var_is_string: Whether the var is a string literal. Deprecated.
184
+ _var_data: Additional hooks and imports associated with the Var.
185
+
186
+ Returns:
187
+ The var.
188
+ """
189
+ var = cls.create(
190
+ value,
191
+ _var_is_local=_var_is_local,
192
+ _var_is_string=_var_is_string,
193
+ _var_data=_var_data,
194
+ )
195
+ assert var is not None
196
+ return var
197
+
198
+ def __format__(self, format_spec: str) -> str:
199
+ """Format the var into a Javascript equivalent to an f-string.
200
+
201
+ Args:
202
+ format_spec: The format specifier (Ignored for now).
203
+
204
+ Returns:
205
+ The formatted var.
206
+ """
207
+ _global_vars[hash(self)] = self
208
+
209
+ # Encode the _var_data into the formatted output for tracking purposes.
210
+ return f"{REFLEX_VAR_OPENING_TAG}{hash(self)}{REFLEX_VAR_CLOSING_TAG}{self._var_name}"
reflex/state.py CHANGED
@@ -8,7 +8,6 @@ import copy
8
8
  import functools
9
9
  import inspect
10
10
  import os
11
- import traceback
12
11
  import uuid
13
12
  from abc import ABC, abstractmethod
14
13
  from collections import defaultdict
@@ -25,9 +24,12 @@ from typing import (
25
24
  Sequence,
26
25
  Set,
27
26
  Type,
27
+ Union,
28
+ cast,
28
29
  )
29
30
 
30
31
  import dill
32
+ from sqlalchemy.orm import DeclarativeBase
31
33
 
32
34
  try:
33
35
  import pydantic.v1 as pydantic
@@ -47,7 +49,6 @@ from reflex.event import (
47
49
  EventHandler,
48
50
  EventSpec,
49
51
  fix_events,
50
- window_alert,
51
52
  )
52
53
  from reflex.utils import console, format, prerequisites, types
53
54
  from reflex.utils.exceptions import ImmutableStateError, LockExpiredError
@@ -425,6 +426,21 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
425
426
  if isinstance(v, ComputedVar)
426
427
  ]
427
428
 
429
+ @classmethod
430
+ def _validate_module_name(cls) -> None:
431
+ """Check if the module name is valid.
432
+
433
+ Reflex uses ___ as state name module separator.
434
+
435
+ Raises:
436
+ NameError: If the module name is invalid.
437
+ """
438
+ if "___" in cls.__module__:
439
+ raise NameError(
440
+ "The module name of a State class cannot contain '___'. "
441
+ "Please rename the module."
442
+ )
443
+
428
444
  @classmethod
429
445
  def __init_subclass__(cls, mixin: bool = False, **kwargs):
430
446
  """Do some magic for the subclass initialization.
@@ -444,8 +460,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
444
460
  if mixin:
445
461
  return
446
462
 
463
+ # Validate the module name.
464
+ cls._validate_module_name()
465
+
447
466
  # Event handlers should not shadow builtin state methods.
448
467
  cls._check_overridden_methods()
468
+
449
469
  # Computed vars should not shadow builtin state props.
450
470
  cls._check_overriden_basevars()
451
471
 
@@ -462,20 +482,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
462
482
  cls.inherited_backend_vars = parent_state.backend_vars
463
483
 
464
484
  # Check if another substate class with the same name has already been defined.
465
- if cls.__name__ in set(c.__name__ for c in parent_state.class_subclasses):
485
+ if cls.get_name() in set(
486
+ c.get_name() for c in parent_state.class_subclasses
487
+ ):
466
488
  if is_testing_env():
467
489
  # Clear existing subclass with same name when app is reloaded via
468
490
  # utils.prerequisites.get_app(reload=True)
469
491
  parent_state.class_subclasses = set(
470
492
  c
471
493
  for c in parent_state.class_subclasses
472
- if c.__name__ != cls.__name__
494
+ if c.get_name() != cls.get_name()
473
495
  )
474
496
  else:
475
497
  # During normal operation, subclasses cannot have the same name, even if they are
476
498
  # defined in different modules.
477
499
  raise StateValueError(
478
- f"The substate class '{cls.__name__}' has been defined multiple times. "
500
+ f"The substate class '{cls.get_name()}' has been defined multiple times. "
479
501
  "Shadowing substate classes is not allowed."
480
502
  )
481
503
  # Track this new subclass in the parent state's subclasses set.
@@ -758,7 +780,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
758
780
  Returns:
759
781
  The name of the state.
760
782
  """
761
- return format.to_snake_case(cls.__name__)
783
+ module = cls.__module__.replace(".", "___")
784
+ return format.to_snake_case(f"{module}___{cls.__name__}")
762
785
 
763
786
  @classmethod
764
787
  @functools.lru_cache()
@@ -1430,15 +1453,39 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1430
1453
  # Convert valid EventHandler and EventSpec into Event
1431
1454
  fixed_events = fix_events(self._check_valid(handler, events), token)
1432
1455
 
1433
- # Get the delta after processing the event.
1434
- delta = state.get_delta()
1435
- state._clean()
1456
+ try:
1457
+ # Get the delta after processing the event.
1458
+ delta = state.get_delta()
1459
+ state._clean()
1460
+
1461
+ return StateUpdate(
1462
+ delta=delta,
1463
+ events=fixed_events,
1464
+ final=final if not handler.is_background else True,
1465
+ )
1466
+ except Exception as ex:
1467
+ state._clean()
1468
+
1469
+ app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
1436
1470
 
1437
- return StateUpdate(
1438
- delta=delta,
1439
- events=fixed_events,
1440
- final=final if not handler.is_background else True,
1441
- )
1471
+ event_specs = app_instance.backend_exception_handler(ex)
1472
+
1473
+ if event_specs is None:
1474
+ return StateUpdate()
1475
+
1476
+ event_specs_correct_type = cast(
1477
+ Union[List[Union[EventSpec, EventHandler]], None],
1478
+ [event_specs] if isinstance(event_specs, EventSpec) else event_specs,
1479
+ )
1480
+ fixed_events = fix_events(
1481
+ event_specs_correct_type,
1482
+ token,
1483
+ router_data=state.router_data,
1484
+ )
1485
+ return StateUpdate(
1486
+ events=fixed_events,
1487
+ final=True,
1488
+ )
1442
1489
 
1443
1490
  async def _process_event(
1444
1491
  self, handler: EventHandler, state: BaseState | StateProxy, payload: Dict
@@ -1491,12 +1538,15 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1491
1538
 
1492
1539
  # If an error occurs, throw a window alert.
1493
1540
  except Exception as ex:
1494
- error = traceback.format_exc()
1495
- print(error)
1496
1541
  telemetry.send_error(ex, context="backend")
1542
+
1543
+ app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
1544
+
1545
+ event_specs = app_instance.backend_exception_handler(ex)
1546
+
1497
1547
  yield state._as_state_update(
1498
1548
  handler,
1499
- window_alert("An error occurred. See logs for details."),
1549
+ event_specs,
1500
1550
  final=True,
1501
1551
  )
1502
1552
 
@@ -1798,6 +1848,23 @@ class State(BaseState):
1798
1848
  is_hydrated: bool = False
1799
1849
 
1800
1850
 
1851
+ class FrontendEventExceptionState(State):
1852
+ """Substate for handling frontend exceptions."""
1853
+
1854
+ def handle_frontend_exception(self, stack: str) -> None:
1855
+ """Handle frontend exceptions.
1856
+
1857
+ If a frontend exception handler is provided, it will be called.
1858
+ Otherwise, the default frontend exception handler will be called.
1859
+
1860
+ Args:
1861
+ stack: The stack trace of the exception.
1862
+
1863
+ """
1864
+ app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
1865
+ app_instance.frontend_exception_handler(Exception(stack))
1866
+
1867
+
1801
1868
  class UpdateVarsInternalState(State):
1802
1869
  """Substate for handling internal state var updates."""
1803
1870
 
@@ -2919,7 +2986,7 @@ class MutableProxy(wrapt.ObjectProxy):
2919
2986
  pydantic.BaseModel.__dict__
2920
2987
  )
2921
2988
 
2922
- __mutable_types__ = (list, dict, set, Base)
2989
+ __mutable_types__ = (list, dict, set, Base, DeclarativeBase)
2923
2990
 
2924
2991
  def __init__(self, wrapped: Any, state: BaseState, field_name: str):
2925
2992
  """Create a proxy for a mutable object that tracks changes.
reflex/testing.py CHANGED
@@ -40,6 +40,7 @@ import reflex
40
40
  import reflex.reflex
41
41
  import reflex.utils.build
42
42
  import reflex.utils.exec
43
+ import reflex.utils.format
43
44
  import reflex.utils.prerequisites
44
45
  import reflex.utils.processes
45
46
  from reflex.state import (
@@ -177,6 +178,33 @@ class AppHarness:
177
178
  app_module_path=root / app_name / f"{app_name}.py",
178
179
  )
179
180
 
181
+ def get_state_name(self, state_cls_name: str) -> str:
182
+ """Get the state name for the given state class name.
183
+
184
+ Args:
185
+ state_cls_name: The state class name
186
+
187
+ Returns:
188
+ The state name
189
+ """
190
+ return reflex.utils.format.to_snake_case(
191
+ f"{self.app_name}___{self.app_name}___" + state_cls_name
192
+ )
193
+
194
+ def get_full_state_name(self, path: List[str]) -> str:
195
+ """Get the full state name for the given state class name.
196
+
197
+ Args:
198
+ path: A list of state class names
199
+
200
+ Returns:
201
+ The full state name
202
+ """
203
+ # NOTE: using State.get_name() somehow causes trouble here
204
+ # path = [State.get_name()] + [self.get_state_name(p) for p in path]
205
+ path = ["reflex___state____state"] + [self.get_state_name(p) for p in path]
206
+ return ".".join(path)
207
+
180
208
  def _get_globals_from_signature(self, func: Any) -> dict[str, Any]:
181
209
  """Get the globals from a function or module object.
182
210
 
reflex/utils/compat.py CHANGED
@@ -32,6 +32,13 @@ def pydantic_v1_patch():
32
32
  Yields:
33
33
  None when the Pydantic module is patched.
34
34
  """
35
+ import pydantic
36
+
37
+ if pydantic.__version__.startswith("1."):
38
+ # pydantic v1 is already installed
39
+ yield
40
+ return
41
+
35
42
  patched_modules = [
36
43
  "pydantic",
37
44
  "pydantic.fields",
@@ -42,11 +49,6 @@ def pydantic_v1_patch():
42
49
  try:
43
50
  import pydantic.v1 # type: ignore
44
51
 
45
- if pydantic.__version__.startswith("1."):
46
- # pydantic v1 is already installed
47
- yield
48
- return
49
-
50
52
  sys.modules["pydantic.fields"] = pydantic.v1.fields # type: ignore
51
53
  sys.modules["pydantic.main"] = pydantic.v1.main # type: ignore
52
54
  sys.modules["pydantic.errors"] = pydantic.v1.errors # type: ignore
reflex/utils/console.py CHANGED
@@ -17,6 +17,9 @@ _LOG_LEVEL = LogLevel.INFO
17
17
  # Deprecated features who's warning has been printed.
18
18
  _EMITTED_DEPRECATION_WARNINGS = set()
19
19
 
20
+ # Info messages which have been printed.
21
+ _EMITTED_INFO = set()
22
+
20
23
 
21
24
  def set_log_level(log_level: LogLevel):
22
25
  """Set the log level.
@@ -62,14 +65,20 @@ def debug(msg: str, **kwargs):
62
65
  print(msg_, **kwargs)
63
66
 
64
67
 
65
- def info(msg: str, **kwargs):
68
+ def info(msg: str, dedupe: bool = False, **kwargs):
66
69
  """Print an info message.
67
70
 
68
71
  Args:
69
72
  msg: The info message.
73
+ dedupe: If True, suppress multiple console logs of info message.
70
74
  kwargs: Keyword arguments to pass to the print function.
71
75
  """
72
76
  if _LOG_LEVEL <= LogLevel.INFO:
77
+ if dedupe:
78
+ if msg in _EMITTED_INFO:
79
+ return
80
+ else:
81
+ _EMITTED_INFO.add(msg)
73
82
  print(f"[cyan]Info: {msg}[/cyan]", **kwargs)
74
83
 
75
84
 
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import functools
6
6
  import glob
7
7
  import importlib
8
+ import importlib.metadata
8
9
  import inspect
9
10
  import json
10
11
  import os
@@ -23,7 +24,6 @@ from types import ModuleType
23
24
  from typing import Callable, List, Optional
24
25
 
25
26
  import httpx
26
- import pkg_resources
27
27
  import typer
28
28
  from alembic.util.exc import CommandError
29
29
  from packaging import version
@@ -61,7 +61,7 @@ class CpuInfo(Base):
61
61
  def get_web_dir() -> Path:
62
62
  """Get the working directory for the next.js commands.
63
63
 
64
- Can be overriden with REFLEX_WEB_WORKDIR.
64
+ Can be overridden with REFLEX_WEB_WORKDIR.
65
65
 
66
66
  Returns:
67
67
  The working directory.
@@ -78,7 +78,7 @@ def check_latest_package_version(package_name: str):
78
78
  """
79
79
  try:
80
80
  # Get the latest version from PyPI
81
- current_version = pkg_resources.get_distribution(package_name).version
81
+ current_version = importlib.metadata.version(package_name)
82
82
  url = f"https://pypi.org/pypi/{package_name}/json"
83
83
  response = httpx.get(url)
84
84
  latest_version = response.json()["info"]["version"]
@@ -404,9 +404,15 @@ def initialize_gitignore(
404
404
  files_to_ignore: The files to add to the .gitignore file.
405
405
  """
406
406
  # Combine with the current ignored files.
407
+ current_ignore: set[str] = set()
407
408
  if os.path.exists(gitignore_file):
408
409
  with open(gitignore_file, "r") as f:
409
- files_to_ignore |= set([line.strip() for line in f.readlines()])
410
+ current_ignore |= set([line.strip() for line in f.readlines()])
411
+
412
+ if files_to_ignore == current_ignore:
413
+ console.debug(f"{gitignore_file} already up to date.")
414
+ return
415
+ files_to_ignore |= current_ignore
410
416
 
411
417
  # Write files to the .gitignore file.
412
418
  with open(gitignore_file, "w", newline="\n") as f:
@@ -970,7 +976,7 @@ def is_latest_template() -> bool:
970
976
  json_file = get_web_dir() / constants.Reflex.JSON
971
977
  if not json_file.exists():
972
978
  return False
973
- app_version = json.load(json_file.open()).get("version")
979
+ app_version = json.loads(json_file.read_text()).get("version")
974
980
  return app_version == constants.Reflex.VERSION
975
981
 
976
982
 
reflex/utils/processes.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import collections
6
6
  import contextlib
7
+ import importlib.metadata
7
8
  import os
8
9
  import signal
9
10
  import subprocess
@@ -58,8 +59,12 @@ def get_process_on_port(port) -> Optional[psutil.Process]:
58
59
  """
59
60
  for proc in psutil.process_iter(["pid", "name", "cmdline"]):
60
61
  try:
61
- for conns in proc.connections(kind="inet"):
62
- if conns.laddr.port == int(port):
62
+ if importlib.metadata.version("psutil") >= "6.0.0":
63
+ conns = proc.net_connections(kind="inet") # type: ignore
64
+ else:
65
+ conns = proc.connections(kind="inet")
66
+ for conn in conns:
67
+ if conn.laddr.port == int(port):
63
68
  return proc
64
69
  except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
65
70
  pass
@@ -159,7 +159,10 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
159
159
  ]
160
160
  )
161
161
 
162
- if value.__name__ not in type_hint_globals:
162
+ if (
163
+ value.__module__ not in ["builtins", "__builtins__"]
164
+ and value.__name__ not in type_hint_globals
165
+ ):
163
166
  raise TypeError(
164
167
  f"{value.__module__ + '.' + value.__name__} is not a default import, "
165
168
  "add it to DEFAULT_IMPORTS in pyi_generator.py"