reflex 0.8.15a0__py3-none-any.whl → 0.8.15a1__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.

@@ -0,0 +1,76 @@
1
+ """A state manager that stores states in memory."""
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import dataclasses
6
+ from collections.abc import AsyncIterator
7
+
8
+ from typing_extensions import override
9
+
10
+ from reflex.istate.manager import StateManager
11
+ from reflex.state import BaseState, _split_substate_key
12
+
13
+
14
+ @dataclasses.dataclass
15
+ class StateManagerMemory(StateManager):
16
+ """A state manager that stores states in memory."""
17
+
18
+ # The mapping of client ids to states.
19
+ states: dict[str, BaseState] = dataclasses.field(default_factory=dict)
20
+
21
+ # The mutex ensures the dict of mutexes is updated exclusively
22
+ _state_manager_lock: asyncio.Lock = dataclasses.field(default=asyncio.Lock())
23
+
24
+ # The dict of mutexes for each client
25
+ _states_locks: dict[str, asyncio.Lock] = dataclasses.field(
26
+ default_factory=dict, init=False
27
+ )
28
+
29
+ @override
30
+ async def get_state(self, token: str) -> BaseState:
31
+ """Get the state for a token.
32
+
33
+ Args:
34
+ token: The token to get the state for.
35
+
36
+ Returns:
37
+ The state for the token.
38
+ """
39
+ # Memory state manager ignores the substate suffix and always returns the top-level state.
40
+ token = _split_substate_key(token)[0]
41
+ if token not in self.states:
42
+ self.states[token] = self.state(_reflex_internal_init=True)
43
+ return self.states[token]
44
+
45
+ @override
46
+ async def set_state(self, token: str, state: BaseState):
47
+ """Set the state for a token.
48
+
49
+ Args:
50
+ token: The token to set the state for.
51
+ state: The state to set.
52
+ """
53
+ token = _split_substate_key(token)[0]
54
+ self.states[token] = state
55
+
56
+ @override
57
+ @contextlib.asynccontextmanager
58
+ async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
59
+ """Modify the state for a token while holding exclusive lock.
60
+
61
+ Args:
62
+ token: The token to modify the state for.
63
+
64
+ Yields:
65
+ The state for the token.
66
+ """
67
+ # Memory state manager ignores the substate suffix and always returns the top-level state.
68
+ token = _split_substate_key(token)[0]
69
+ if token not in self._states_locks:
70
+ async with self._state_manager_lock:
71
+ if token not in self._states_locks:
72
+ self._states_locks[token] = asyncio.Lock()
73
+
74
+ async with self._states_locks[token]:
75
+ state = await self.get_state(token)
76
+ yield state
@@ -1,387 +1,29 @@
1
- """State manager for managing client states."""
1
+ """A state manager that stores states in redis."""
2
2
 
3
3
  import asyncio
4
4
  import contextlib
5
5
  import dataclasses
6
- import functools
7
6
  import time
8
7
  import uuid
9
- from abc import ABC, abstractmethod
10
8
  from collections.abc import AsyncIterator
11
- from hashlib import md5
12
- from pathlib import Path
13
9
 
14
10
  from redis import ResponseError
15
11
  from redis.asyncio import Redis
16
12
  from redis.asyncio.client import PubSub
17
13
  from typing_extensions import override
18
14
 
19
- from reflex import constants
20
15
  from reflex.config import get_config
21
16
  from reflex.environment import environment
17
+ from reflex.istate.manager import StateManager, _default_token_expiration
22
18
  from reflex.state import BaseState, _split_substate_key, _substate_key
23
- from reflex.utils import console, path_ops, prerequisites
19
+ from reflex.utils import console
24
20
  from reflex.utils.exceptions import (
25
21
  InvalidLockWarningThresholdError,
26
- InvalidStateManagerModeError,
27
22
  LockExpiredError,
28
23
  StateSchemaMismatchError,
29
24
  )
30
25
 
31
26
 
32
- @dataclasses.dataclass
33
- class StateManager(ABC):
34
- """A class to manage many client states."""
35
-
36
- # The state class to use.
37
- state: type[BaseState]
38
-
39
- @classmethod
40
- def create(cls, state: type[BaseState]):
41
- """Create a new state manager.
42
-
43
- Args:
44
- state: The state class to use.
45
-
46
- Raises:
47
- InvalidStateManagerModeError: If the state manager mode is invalid.
48
-
49
- Returns:
50
- The state manager (either disk, memory or redis).
51
- """
52
- config = get_config()
53
- if prerequisites.parse_redis_url() is not None:
54
- config.state_manager_mode = constants.StateManagerMode.REDIS
55
- if config.state_manager_mode == constants.StateManagerMode.MEMORY:
56
- return StateManagerMemory(state=state)
57
- if config.state_manager_mode == constants.StateManagerMode.DISK:
58
- return StateManagerDisk(state=state)
59
- if config.state_manager_mode == constants.StateManagerMode.REDIS:
60
- redis = prerequisites.get_redis()
61
- if redis is not None:
62
- # make sure expiration values are obtained only from the config object on creation
63
- return StateManagerRedis(
64
- state=state,
65
- redis=redis,
66
- token_expiration=config.redis_token_expiration,
67
- lock_expiration=config.redis_lock_expiration,
68
- lock_warning_threshold=config.redis_lock_warning_threshold,
69
- )
70
- msg = f"Expected one of: DISK, MEMORY, REDIS, got {config.state_manager_mode}"
71
- raise InvalidStateManagerModeError(msg)
72
-
73
- @abstractmethod
74
- async def get_state(self, token: str) -> BaseState:
75
- """Get the state for a token.
76
-
77
- Args:
78
- token: The token to get the state for.
79
-
80
- Returns:
81
- The state for the token.
82
- """
83
-
84
- @abstractmethod
85
- async def set_state(self, token: str, state: BaseState):
86
- """Set the state for a token.
87
-
88
- Args:
89
- token: The token to set the state for.
90
- state: The state to set.
91
- """
92
-
93
- @abstractmethod
94
- @contextlib.asynccontextmanager
95
- async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
96
- """Modify the state for a token while holding exclusive lock.
97
-
98
- Args:
99
- token: The token to modify the state for.
100
-
101
- Yields:
102
- The state for the token.
103
- """
104
- yield self.state()
105
-
106
-
107
- @dataclasses.dataclass
108
- class StateManagerMemory(StateManager):
109
- """A state manager that stores states in memory."""
110
-
111
- # The mapping of client ids to states.
112
- states: dict[str, BaseState] = dataclasses.field(default_factory=dict)
113
-
114
- # The mutex ensures the dict of mutexes is updated exclusively
115
- _state_manager_lock: asyncio.Lock = dataclasses.field(default=asyncio.Lock())
116
-
117
- # The dict of mutexes for each client
118
- _states_locks: dict[str, asyncio.Lock] = dataclasses.field(
119
- default_factory=dict, init=False
120
- )
121
-
122
- @override
123
- async def get_state(self, token: str) -> BaseState:
124
- """Get the state for a token.
125
-
126
- Args:
127
- token: The token to get the state for.
128
-
129
- Returns:
130
- The state for the token.
131
- """
132
- # Memory state manager ignores the substate suffix and always returns the top-level state.
133
- token = _split_substate_key(token)[0]
134
- if token not in self.states:
135
- self.states[token] = self.state(_reflex_internal_init=True)
136
- return self.states[token]
137
-
138
- @override
139
- async def set_state(self, token: str, state: BaseState):
140
- """Set the state for a token.
141
-
142
- Args:
143
- token: The token to set the state for.
144
- state: The state to set.
145
- """
146
- token = _split_substate_key(token)[0]
147
- self.states[token] = state
148
-
149
- @override
150
- @contextlib.asynccontextmanager
151
- async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
152
- """Modify the state for a token while holding exclusive lock.
153
-
154
- Args:
155
- token: The token to modify the state for.
156
-
157
- Yields:
158
- The state for the token.
159
- """
160
- # Memory state manager ignores the substate suffix and always returns the top-level state.
161
- token = _split_substate_key(token)[0]
162
- if token not in self._states_locks:
163
- async with self._state_manager_lock:
164
- if token not in self._states_locks:
165
- self._states_locks[token] = asyncio.Lock()
166
-
167
- async with self._states_locks[token]:
168
- state = await self.get_state(token)
169
- yield state
170
-
171
-
172
- def _default_token_expiration() -> int:
173
- """Get the default token expiration time.
174
-
175
- Returns:
176
- The default token expiration time.
177
- """
178
- return get_config().redis_token_expiration
179
-
180
-
181
- def reset_disk_state_manager():
182
- """Reset the disk state manager."""
183
- console.debug("Resetting disk state manager.")
184
- states_directory = prerequisites.get_states_dir()
185
- if states_directory.exists():
186
- for path in states_directory.iterdir():
187
- path.unlink()
188
-
189
-
190
- @dataclasses.dataclass
191
- class StateManagerDisk(StateManager):
192
- """A state manager that stores states in memory."""
193
-
194
- # The mapping of client ids to states.
195
- states: dict[str, BaseState] = dataclasses.field(default_factory=dict)
196
-
197
- # The mutex ensures the dict of mutexes is updated exclusively
198
- _state_manager_lock: asyncio.Lock = dataclasses.field(default=asyncio.Lock())
199
-
200
- # The dict of mutexes for each client
201
- _states_locks: dict[str, asyncio.Lock] = dataclasses.field(
202
- default_factory=dict,
203
- init=False,
204
- )
205
-
206
- # The token expiration time (s).
207
- token_expiration: int = dataclasses.field(default_factory=_default_token_expiration)
208
-
209
- def __post_init_(self):
210
- """Create a new state manager."""
211
- path_ops.mkdir(self.states_directory)
212
-
213
- self._purge_expired_states()
214
-
215
- @functools.cached_property
216
- def states_directory(self) -> Path:
217
- """Get the states directory.
218
-
219
- Returns:
220
- The states directory.
221
- """
222
- return prerequisites.get_states_dir()
223
-
224
- def _purge_expired_states(self):
225
- """Purge expired states from the disk."""
226
- import time
227
-
228
- for path in path_ops.ls(self.states_directory):
229
- # check path is a pickle file
230
- if path.suffix != ".pkl":
231
- continue
232
-
233
- # load last edited field from file
234
- last_edited = path.stat().st_mtime
235
-
236
- # check if the file is older than the token expiration time
237
- if time.time() - last_edited > self.token_expiration:
238
- # remove the file
239
- path.unlink()
240
-
241
- def token_path(self, token: str) -> Path:
242
- """Get the path for a token.
243
-
244
- Args:
245
- token: The token to get the path for.
246
-
247
- Returns:
248
- The path for the token.
249
- """
250
- return (
251
- self.states_directory / f"{md5(token.encode()).hexdigest()}.pkl"
252
- ).absolute()
253
-
254
- async def load_state(self, token: str) -> BaseState | None:
255
- """Load a state object based on the provided token.
256
-
257
- Args:
258
- token: The token used to identify the state object.
259
-
260
- Returns:
261
- The loaded state object or None.
262
- """
263
- token_path = self.token_path(token)
264
-
265
- if token_path.exists():
266
- try:
267
- with token_path.open(mode="rb") as file:
268
- return BaseState._deserialize(fp=file)
269
- except Exception:
270
- pass
271
- return None
272
-
273
- async def populate_substates(
274
- self, client_token: str, state: BaseState, root_state: BaseState
275
- ):
276
- """Populate the substates of a state object.
277
-
278
- Args:
279
- client_token: The client token.
280
- state: The state object to populate.
281
- root_state: The root state object.
282
- """
283
- for substate in state.get_substates():
284
- substate_token = _substate_key(client_token, substate)
285
-
286
- fresh_instance = await root_state.get_state(substate)
287
- instance = await self.load_state(substate_token)
288
- if instance is not None:
289
- # Ensure all substates exist, even if they weren't serialized previously.
290
- instance.substates = fresh_instance.substates
291
- else:
292
- instance = fresh_instance
293
- state.substates[substate.get_name()] = instance
294
- instance.parent_state = state
295
-
296
- await self.populate_substates(client_token, instance, root_state)
297
-
298
- @override
299
- async def get_state(
300
- self,
301
- token: str,
302
- ) -> BaseState:
303
- """Get the state for a token.
304
-
305
- Args:
306
- token: The token to get the state for.
307
-
308
- Returns:
309
- The state for the token.
310
- """
311
- client_token = _split_substate_key(token)[0]
312
- root_state = self.states.get(client_token)
313
- if root_state is not None:
314
- # Retrieved state from memory.
315
- return root_state
316
-
317
- # Deserialize root state from disk.
318
- root_state = await self.load_state(_substate_key(client_token, self.state))
319
- # Create a new root state tree with all substates instantiated.
320
- fresh_root_state = self.state(_reflex_internal_init=True)
321
- if root_state is None:
322
- root_state = fresh_root_state
323
- else:
324
- # Ensure all substates exist, even if they were not serialized previously.
325
- root_state.substates = fresh_root_state.substates
326
- self.states[client_token] = root_state
327
- await self.populate_substates(client_token, root_state, root_state)
328
- return root_state
329
-
330
- async def set_state_for_substate(self, client_token: str, substate: BaseState):
331
- """Set the state for a substate.
332
-
333
- Args:
334
- client_token: The client token.
335
- substate: The substate to set.
336
- """
337
- substate_token = _substate_key(client_token, substate)
338
-
339
- if substate._get_was_touched():
340
- substate._was_touched = False # Reset the touched flag after serializing.
341
- pickle_state = substate._serialize()
342
- if pickle_state:
343
- if not self.states_directory.exists():
344
- self.states_directory.mkdir(parents=True, exist_ok=True)
345
- self.token_path(substate_token).write_bytes(pickle_state)
346
-
347
- for substate_substate in substate.substates.values():
348
- await self.set_state_for_substate(client_token, substate_substate)
349
-
350
- @override
351
- async def set_state(self, token: str, state: BaseState):
352
- """Set the state for a token.
353
-
354
- Args:
355
- token: The token to set the state for.
356
- state: The state to set.
357
- """
358
- client_token, _ = _split_substate_key(token)
359
- await self.set_state_for_substate(client_token, state)
360
-
361
- @override
362
- @contextlib.asynccontextmanager
363
- async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
364
- """Modify the state for a token while holding exclusive lock.
365
-
366
- Args:
367
- token: The token to modify the state for.
368
-
369
- Yields:
370
- The state for the token.
371
- """
372
- # Memory state manager ignores the substate suffix and always returns the top-level state.
373
- client_token, _ = _split_substate_key(token)
374
- if client_token not in self._states_locks:
375
- async with self._state_manager_lock:
376
- if client_token not in self._states_locks:
377
- self._states_locks[client_token] = asyncio.Lock()
378
-
379
- async with self._states_locks[client_token]:
380
- state = await self.get_state(token)
381
- yield state
382
- await self.set_state(token, state)
383
-
384
-
385
27
  def _default_lock_expiration() -> int:
386
28
  """Get the default lock expiration time.
387
29
 
@@ -748,7 +390,7 @@ class StateManagerRedis(StateManager):
748
390
  if timeout is None:
749
391
  timeout = self.lock_expiration / 1000.0
750
392
 
751
- started = time.time()
393
+ started = time.monotonic()
752
394
  message = await pubsub.get_message(
753
395
  ignore_subscribe_messages=True,
754
396
  timeout=timeout,
@@ -757,7 +399,7 @@ class StateManagerRedis(StateManager):
757
399
  message is None
758
400
  or message["data"] not in self._redis_keyspace_lock_release_events
759
401
  ):
760
- remaining = timeout - (time.time() - started)
402
+ remaining = timeout - (time.monotonic() - started)
761
403
  if remaining <= 0:
762
404
  return
763
405
  await self._get_pubsub_message(pubsub, timeout=remaining)
@@ -847,12 +489,3 @@ class StateManagerRedis(StateManager):
847
489
  Note: Connections will be automatically reopened when needed.
848
490
  """
849
491
  await self.redis.aclose(close_connection_pool=True)
850
-
851
-
852
- def get_state_manager() -> StateManager:
853
- """Get the state manager for the app that is currently running.
854
-
855
- Returns:
856
- The state manager.
857
- """
858
- return prerequisites.get_and_validate_app().app.state_manager
reflex/model.py CHANGED
@@ -71,6 +71,10 @@ if find_spec("sqlalchemy"):
71
71
  "echo": environment.SQLALCHEMY_ECHO.get(),
72
72
  # Check connections before returning them.
73
73
  "pool_pre_ping": environment.SQLALCHEMY_POOL_PRE_PING.get(),
74
+ "pool_size": environment.SQLALCHEMY_POOL_SIZE.get(),
75
+ "max_overflow": environment.SQLALCHEMY_MAX_OVERFLOW.get(),
76
+ "pool_recycle": environment.SQLALCHEMY_POOL_RECYCLE.get(),
77
+ "pool_timeout": environment.SQLALCHEMY_POOL_TIMEOUT.get(),
74
78
  }
75
79
  conf = get_config()
76
80
  url = url or conf.db_url
@@ -363,7 +367,7 @@ if find_spec("sqlmodel") and find_spec("sqlalchemy") and find_spec("pydantic"):
363
367
  reason=(
364
368
  "Register sqlmodel.SQLModel classes with `@rx.ModelRegistry.register`"
365
369
  ),
366
- deprecation_version="0.8.0",
370
+ deprecation_version="0.8.15",
367
371
  removal_version="0.9.0",
368
372
  )
369
373
  super().__pydantic_init_subclass__()
reflex/state.py CHANGED
@@ -16,6 +16,7 @@ import time
16
16
  import typing
17
17
  import warnings
18
18
  from collections.abc import AsyncIterator, Callable, Sequence
19
+ from enum import Enum
19
20
  from hashlib import md5
20
21
  from importlib.util import find_spec
21
22
  from types import FunctionType
@@ -246,7 +247,7 @@ class EventHandlerSetVar(EventHandler):
246
247
  msg = f"Variable `{args[0]}` cannot be set on `{self.state_cls.get_full_name()}`"
247
248
  raise AttributeError(msg)
248
249
 
249
- if asyncio.iscoroutinefunction(handler.fn):
250
+ if inspect.iscoroutinefunction(handler.fn):
250
251
  msg = f"Setter for {args[0]} is async, which is not supported."
251
252
  raise NotImplementedError(msg)
252
253
 
@@ -287,7 +288,7 @@ async def _resolve_delta(delta: Delta) -> Delta:
287
288
  tasks = {}
288
289
  for state_name, state_delta in delta.items():
289
290
  for var_name, value in state_delta.items():
290
- if asyncio.iscoroutine(value):
291
+ if inspect.iscoroutine(value):
291
292
  tasks[state_name, var_name] = asyncio.create_task(
292
293
  value,
293
294
  name=f"reflex_resolve_delta|{state_name}|{var_name}|{time.time()}",
@@ -852,7 +853,7 @@ class BaseState(EvenMoreBasicBaseState):
852
853
  ComputedVarShadowsBaseVarsError: When a computed var shadows a base var.
853
854
  """
854
855
  for name, computed_var_ in cls._get_computed_vars():
855
- if name in cls.__annotations__:
856
+ if name in get_type_hints(cls):
856
857
  msg = f"The computed var name `{computed_var_._js_expr}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
857
858
  raise ComputedVarShadowsBaseVarsError(msg)
858
859
 
@@ -1554,6 +1555,8 @@ class BaseState(EvenMoreBasicBaseState):
1554
1555
  RuntimeError: If redis is not used in this backend process.
1555
1556
  StateMismatchError: If the state instance is not of the expected type.
1556
1557
  """
1558
+ from reflex.istate.manager.redis import StateManagerRedis
1559
+
1557
1560
  # Then get the target state and all its substates.
1558
1561
  state_manager = get_state_manager()
1559
1562
  if not isinstance(state_manager, StateManagerRedis):
@@ -1733,7 +1736,7 @@ class BaseState(EvenMoreBasicBaseState):
1733
1736
  except TypeError:
1734
1737
  pass
1735
1738
 
1736
- coroutines = [e for e in events if asyncio.iscoroutine(e)]
1739
+ coroutines = [e for e in events if inspect.iscoroutine(e)]
1737
1740
 
1738
1741
  for coroutine in coroutines:
1739
1742
  coroutine_name = coroutine.__qualname__
@@ -1878,6 +1881,12 @@ class BaseState(EvenMoreBasicBaseState):
1878
1881
  hinted_args is tuple or hinted_args is tuple
1879
1882
  ):
1880
1883
  payload[arg] = tuple(value)
1884
+ elif isinstance(hinted_args, type) and issubclass(hinted_args, Enum):
1885
+ try:
1886
+ payload[arg] = hinted_args(value)
1887
+ except ValueError:
1888
+ msg = f"Received an invalid enum value ({value}) for {arg} of type {hinted_args}"
1889
+ raise ValueError(msg) from None
1881
1890
  elif (
1882
1891
  isinstance(value, str)
1883
1892
  and (deserializer := _deserializers.get(hinted_args)) is not None
@@ -1895,7 +1904,7 @@ class BaseState(EvenMoreBasicBaseState):
1895
1904
  # Wrap the function in a try/except block.
1896
1905
  try:
1897
1906
  # Handle async functions.
1898
- if asyncio.iscoroutinefunction(fn.func):
1907
+ if inspect.iscoroutinefunction(fn.func):
1899
1908
  events = await fn(**payload)
1900
1909
 
1901
1910
  # Handle regular functions.
@@ -2738,11 +2747,7 @@ def reload_state_module(
2738
2747
  state.get_class_substate.cache_clear()
2739
2748
 
2740
2749
 
2741
- from reflex.istate.manager import LockExpiredError as LockExpiredError # noqa: E402
2742
2750
  from reflex.istate.manager import StateManager as StateManager # noqa: E402
2743
- from reflex.istate.manager import StateManagerDisk as StateManagerDisk # noqa: E402
2744
- from reflex.istate.manager import StateManagerMemory as StateManagerMemory # noqa: E402
2745
- from reflex.istate.manager import StateManagerRedis as StateManagerRedis # noqa: E402
2746
2751
  from reflex.istate.manager import get_state_manager as get_state_manager # noqa: E402
2747
2752
  from reflex.istate.manager import ( # noqa: E402
2748
2753
  reset_disk_state_manager as reset_disk_state_manager,
reflex/testing.py CHANGED
@@ -38,14 +38,10 @@ import reflex.utils.processes
38
38
  from reflex.components.component import CustomComponent
39
39
  from reflex.config import get_config
40
40
  from reflex.environment import environment
41
- from reflex.state import (
42
- BaseState,
43
- StateManager,
44
- StateManagerDisk,
45
- StateManagerMemory,
46
- StateManagerRedis,
47
- reload_state_module,
48
- )
41
+ from reflex.istate.manager.disk import StateManagerDisk
42
+ from reflex.istate.manager.memory import StateManagerMemory
43
+ from reflex.istate.manager.redis import StateManagerRedis
44
+ from reflex.state import BaseState, StateManager, reload_state_module
49
45
  from reflex.utils import console, js_runtimes
50
46
  from reflex.utils.export import export
51
47
  from reflex.utils.token_manager import TokenManager
reflex/utils/compat.py CHANGED
@@ -1,6 +1,9 @@
1
1
  """Compatibility hacks and helpers."""
2
2
 
3
- from typing import TYPE_CHECKING
3
+ import sys
4
+ from collections.abc import Mapping
5
+ from importlib.util import find_spec
6
+ from typing import TYPE_CHECKING, Any
4
7
 
5
8
  if TYPE_CHECKING:
6
9
  from pydantic.fields import FieldInfo
@@ -30,6 +33,51 @@ async def windows_hot_reload_lifespan_hack():
30
33
  pass
31
34
 
32
35
 
36
+ def annotations_from_namespace(namespace: Mapping[str, Any]) -> dict[str, Any]:
37
+ """Get the annotations from a class namespace.
38
+
39
+ Args:
40
+ namespace: The class namespace.
41
+
42
+ Returns:
43
+ The (forward-ref) annotations from the class namespace.
44
+ """
45
+ if sys.version_info >= (3, 14) and "__annotations__" not in namespace:
46
+ from annotationlib import (
47
+ Format,
48
+ call_annotate_function,
49
+ get_annotate_from_class_namespace,
50
+ )
51
+
52
+ if annotate := get_annotate_from_class_namespace(namespace):
53
+ return call_annotate_function(annotate, format=Format.FORWARDREF)
54
+ return namespace.get("__annotations__", {})
55
+
56
+
57
+ if find_spec("pydantic") and find_spec("pydantic.v1"):
58
+ from pydantic.v1.main import ModelMetaclass
59
+
60
+ class ModelMetaclassLazyAnnotations(ModelMetaclass):
61
+ """Compatibility metaclass to resolve python3.14 style lazy annotations."""
62
+
63
+ def __new__(mcs, name: str, bases: tuple, namespace: dict, **kwargs):
64
+ """Resolve python3.14 style lazy annotations before passing off to pydantic v1.
65
+
66
+ Args:
67
+ name: The class name.
68
+ bases: The base classes.
69
+ namespace: The class namespace.
70
+ **kwargs: Additional keyword arguments.
71
+
72
+ Returns:
73
+ The created class.
74
+ """
75
+ namespace["__annotations__"] = annotations_from_namespace(namespace)
76
+ return super().__new__(mcs, name, bases, namespace, **kwargs)
77
+ else:
78
+ ModelMetaclassLazyAnnotations = type # type: ignore[assignment]
79
+
80
+
33
81
  def sqlmodel_field_has_primary_key(field_info: "FieldInfo") -> bool:
34
82
  """Determines if a field is a primary.
35
83