123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669 |
- """Collection of base classes."""
- from __future__ import annotations
- import contextlib
- import dataclasses
- import datetime
- import functools
- import inspect
- import json
- import random
- import re
- import string
- import uuid
- import warnings
- from types import CodeType, EllipsisType, FunctionType
- from typing import (
- TYPE_CHECKING,
- Any,
- Callable,
- ClassVar,
- Coroutine,
- Dict,
- FrozenSet,
- Generic,
- Iterable,
- List,
- Literal,
- Mapping,
- NoReturn,
- Optional,
- Sequence,
- Set,
- Tuple,
- Type,
- TypeVar,
- Union,
- cast,
- get_args,
- overload,
- )
- from sqlalchemy.orm import DeclarativeBase
- from typing_extensions import (
- ParamSpec,
- Protocol,
- TypeGuard,
- deprecated,
- get_type_hints,
- override,
- )
- from reflex import constants
- from reflex.base import Base
- from reflex.constants.compiler import Hooks
- from reflex.utils import console, exceptions, imports, serializers, types
- from reflex.utils.exceptions import (
- ComputedVarSignatureError,
- UntypedComputedVarError,
- VarDependencyError,
- VarTypeError,
- )
- from reflex.utils.format import format_state_name
- from reflex.utils.imports import (
- ImmutableParsedImportDict,
- ImportDict,
- ImportVar,
- ParsedImportDict,
- parse_imports,
- )
- from reflex.utils.types import (
- GenericType,
- Self,
- _isinstance,
- get_origin,
- has_args,
- safe_issubclass,
- typehint_issubclass,
- unionize,
- )
- if TYPE_CHECKING:
- from reflex.components.component import BaseComponent
- from reflex.state import BaseState
- from .function import ArgsFunctionOperation
- from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar
- from .object import LiteralObjectVar, ObjectVar
- from .sequence import ArrayVar, LiteralArrayVar, LiteralStringVar, StringVar
- VAR_TYPE = TypeVar("VAR_TYPE", covariant=True)
- VALUE = TypeVar("VALUE")
- INT_OR_FLOAT = TypeVar("INT_OR_FLOAT", int, float)
- FAKE_VAR_TYPE = TypeVar("FAKE_VAR_TYPE")
- OTHER_VAR_TYPE = TypeVar("OTHER_VAR_TYPE")
- STRING_T = TypeVar("STRING_T", bound=str)
- SEQUENCE_TYPE = TypeVar("SEQUENCE_TYPE", bound=Sequence)
- warnings.filterwarnings("ignore", message="fields may not start with an underscore")
- P = ParamSpec("P")
- R = TypeVar("R")
- class ReflexCallable(Protocol[P, R]):
- """Protocol for a callable."""
- __call__: Callable[P, R]
- ReflexCallableParams = Union[EllipsisType, Tuple[GenericType, ...]]
- def unwrap_reflex_callalbe(
- callable_type: GenericType,
- ) -> Tuple[ReflexCallableParams, GenericType]:
- """Unwrap the ReflexCallable type.
- Args:
- callable_type: The ReflexCallable type to unwrap.
- Returns:
- The unwrapped ReflexCallable type.
- """
- if callable_type is ReflexCallable:
- return Ellipsis, Any
- origin = get_origin(callable_type)
- if origin is not ReflexCallable:
- if origin in types.UnionTypes:
- args = get_args(callable_type)
- params: List[ReflexCallableParams] = []
- return_types: List[GenericType] = []
- for arg in args:
- param, return_type = unwrap_reflex_callalbe(arg)
- if param not in params:
- params.append(param)
- return_types.append(return_type)
- return (
- Ellipsis if len(params) > 1 else params[0],
- unionize(*return_types),
- )
- return Ellipsis, Any
- args = get_args(callable_type)
- if not args or len(args) != 2:
- return Ellipsis, Any
- return args
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- )
- class VarSubclassEntry:
- """Entry for a Var subclass."""
- var_subclass: Type[Var]
- to_var_subclass: Type[ToOperation]
- python_types: Tuple[GenericType, ...]
- is_subclass: Callable[[GenericType], bool] | None
- _var_subclasses: List[VarSubclassEntry] = []
- _var_literal_subclasses: List[Tuple[Type[LiteralVar], VarSubclassEntry]] = []
- @dataclasses.dataclass(
- eq=True,
- frozen=True,
- )
- class VarData:
- """Metadata associated with a x."""
- # The name of the enclosing state.
- state: str = dataclasses.field(default="")
- # The name of the field in the state.
- field_name: str = dataclasses.field(default="")
- # Imports needed to render this var
- imports: ImmutableParsedImportDict = dataclasses.field(default_factory=tuple)
- # Hooks that need to be present in the component to render this var
- hooks: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
- # Components that need to be present in the component to render this var
- components: Tuple[BaseComponent, ...] = dataclasses.field(default_factory=tuple)
- # Dependencies of the var
- deps: Tuple[Var, ...] = dataclasses.field(default_factory=tuple)
- # Position of the hook in the component
- position: Hooks.HookPosition | None = None
- def __init__(
- self,
- state: str = "",
- field_name: str = "",
- imports: ImportDict | ParsedImportDict | None = None,
- hooks: Mapping[str, VarData | None] | Sequence[str] | str | None = None,
- components: Iterable[BaseComponent] | None = None,
- deps: list[Var] | None = None,
- position: Hooks.HookPosition | None = None,
- ):
- """Initialize the var data.
- Args:
- state: The name of the enclosing state.
- field_name: The name of the field in the state.
- imports: Imports needed to render this var.
- hooks: Hooks that need to be present in the component to render this var.
- components: Components that need to be present in the component to render this var.
- deps: Dependencies of the var for useCallback.
- position: Position of the hook in the component.
- """
- if isinstance(hooks, str):
- hooks = [hooks]
- if not isinstance(hooks, dict):
- hooks = {hook: None for hook in (hooks or [])}
- immutable_imports: ImmutableParsedImportDict = tuple(
- (k, tuple(v)) for k, v in parse_imports(imports or {}).items()
- )
- object.__setattr__(self, "state", state)
- object.__setattr__(self, "field_name", field_name)
- object.__setattr__(self, "imports", immutable_imports)
- object.__setattr__(self, "hooks", tuple(hooks or {}))
- object.__setattr__(
- self, "components", tuple(components) if components is not None else ()
- )
- object.__setattr__(self, "deps", tuple(deps or []))
- object.__setattr__(self, "position", position or None)
- if hooks and any(hooks.values()):
- merged_var_data = VarData.merge(self, *hooks.values())
- if merged_var_data is not None:
- object.__setattr__(self, "state", merged_var_data.state)
- object.__setattr__(self, "field_name", merged_var_data.field_name)
- object.__setattr__(self, "imports", merged_var_data.imports)
- object.__setattr__(self, "hooks", merged_var_data.hooks)
- object.__setattr__(self, "deps", merged_var_data.deps)
- object.__setattr__(self, "position", merged_var_data.position)
- def old_school_imports(self) -> ImportDict:
- """Return the imports as a mutable dict.
- Returns:
- The imports as a mutable dict.
- """
- return {k: list(v) for k, v in self.imports}
- def merge(*all: VarData | None) -> VarData | None:
- """Merge multiple var data objects.
- Returns:
- The merged var data object.
- Raises:
- ReflexError: If the positions of the var data objects are different.
- """
- all_var_datas = list(filter(None, all))
- if not all_var_datas:
- return None
- if len(all_var_datas) == 1:
- return all_var_datas[0]
- # Get the first non-empty field name or default to empty string.
- field_name = next(
- (var_data.field_name for var_data in all_var_datas if var_data.field_name),
- "",
- )
- # Get the first non-empty state or default to empty string.
- state = next(
- (var_data.state for var_data in all_var_datas if var_data.state), ""
- )
- hooks: dict[str, VarData | None] = {
- hook: None for var_data in all_var_datas for hook in var_data.hooks
- }
- _imports = imports.merge_imports(
- *(var_data.imports for var_data in all_var_datas)
- )
- deps = [dep for var_data in all_var_datas for dep in var_data.deps]
- positions = list(
- {
- var_data.position
- for var_data in all_var_datas
- if var_data.position is not None
- }
- )
- components = tuple(
- component for var_data in all_var_datas for component in var_data.components
- )
- if positions:
- if len(positions) > 1:
- raise exceptions.ReflexError(
- f"Cannot merge var data with different positions: {positions}"
- )
- position = positions[0]
- else:
- position = None
- return VarData(
- state=state,
- field_name=field_name,
- imports=_imports,
- hooks=hooks,
- deps=deps,
- position=position,
- components=components,
- )
- def __bool__(self) -> bool:
- """Check if the var data is non-empty.
- Returns:
- True if any field is set to a non-default value.
- """
- return any(getattr(self, field.name) for field in dataclasses.fields(self))
- @classmethod
- def from_state(cls, state: Type[BaseState] | str, field_name: str = "") -> VarData:
- """Set the state of the var.
- Args:
- state: The state to set or the full name of the state.
- field_name: The name of the field in the state. Optional.
- Returns:
- The var with the set state.
- """
- from reflex.utils import format
- state_name = state if isinstance(state, str) else state.get_full_name()
- return VarData(
- state=state_name,
- field_name=field_name,
- hooks={
- "const {0} = useContext(StateContexts.{0})".format(
- format.format_state_name(state_name)
- ): None
- },
- imports={
- f"$/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
- "react": [ImportVar(tag="useContext")],
- },
- )
- def _decode_var_immutable(value: str) -> tuple[VarData | None, str]:
- """Decode the state name from a formatted var.
- Args:
- value: The value to extract the state name from.
- Returns:
- The extracted state name and the value without the state name.
- """
- var_datas = []
- if isinstance(value, str):
- # fast path if there is no encoded VarData
- if constants.REFLEX_VAR_OPENING_TAG not in value:
- return None, value
- offset = 0
- # Find all tags.
- while m := _decode_var_pattern.search(value):
- start, end = m.span()
- value = value[:start] + value[end:]
- serialized_data = m.group(1)
- if serialized_data.isnumeric() or (
- serialized_data[0] == "-" and serialized_data[1:].isnumeric()
- ):
- # This is a global immutable var.
- var = _global_vars[int(serialized_data)]
- var_data = var._get_all_var_data()
- if var_data is not None:
- var_datas.append(var_data)
- offset += end - start
- return VarData.merge(*var_datas) if var_datas else None, value
- def can_use_in_object_var(cls: GenericType) -> bool:
- """Check if the class can be used in an ObjectVar.
- Args:
- cls: The class to check.
- Returns:
- Whether the class can be used in an ObjectVar.
- """
- if types.is_union(cls):
- return all(can_use_in_object_var(t) for t in types.get_args(cls))
- return (
- inspect.isclass(cls)
- and not issubclass(cls, Var)
- and serializers.can_serialize(cls, dict)
- )
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- )
- class Var(Generic[VAR_TYPE]):
- """Base class for immutable vars."""
- # The name of the var.
- _js_expr: str = dataclasses.field()
- # The type of the var.
- _var_type: types.GenericType = dataclasses.field(default=Any)
- # Extra metadata associated with the Var
- _var_data: Optional[VarData] = dataclasses.field(default=None)
- def __str__(self) -> str:
- """String representation of the var. Guaranteed to be a valid Javascript expression.
- Returns:
- The name of the var.
- """
- return self._js_expr
- @property
- def _var_is_local(self) -> bool:
- """Whether this is a local javascript variable.
- Returns:
- False
- """
- return False
- @property
- @deprecated("Use `_js_expr` instead.")
- def _var_name(self) -> str:
- """The name of the var.
- Returns:
- The name of the var.
- """
- return self._js_expr
- @property
- def _var_field_name(self) -> str:
- """The name of the field.
- Returns:
- The name of the field.
- """
- var_data = self._get_all_var_data()
- field_name = var_data.field_name if var_data else None
- return field_name or self._js_expr
- @property
- @deprecated("Use `_js_expr` instead.")
- def _var_name_unwrapped(self) -> str:
- """The name of the var without extra curly braces.
- Returns:
- The name of the var.
- """
- return self._js_expr
- @property
- def _var_is_string(self) -> bool:
- """Whether the var is a string literal.
- Returns:
- False
- """
- return False
- def __init_subclass__(
- cls,
- python_types: Tuple[GenericType, ...] | GenericType = types.Unset(),
- default_type: GenericType = types.Unset(),
- is_subclass: Callable[[GenericType], bool] | types.Unset = types.Unset(),
- **kwargs,
- ):
- """Initialize the subclass.
- Args:
- python_types: The python types that the var represents.
- default_type: The default type of the var. Defaults to the first python type.
- is_subclass: A function to check if a type is a subclass of the var.
- **kwargs: Additional keyword arguments.
- """
- super().__init_subclass__(**kwargs)
- if python_types or default_type or is_subclass:
- python_types = (
- (python_types if isinstance(python_types, tuple) else (python_types,))
- if python_types
- else ()
- )
- default_type = default_type or (python_types[0] if python_types else Any)
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- slots=True,
- )
- class ToVarOperation(ToOperation, cls):
- """Base class of converting a var to another var type."""
- _original: Var = dataclasses.field(
- default=Var(_js_expr="null", _var_type=None),
- )
- _default_var_type: ClassVar[GenericType] = default_type
- new_to_var_operation_name = f"To{cls.__name__.removesuffix('Var')}Operation"
- ToVarOperation.__qualname__ = (
- ToVarOperation.__qualname__.removesuffix(ToVarOperation.__name__)
- + new_to_var_operation_name
- )
- ToVarOperation.__name__ = new_to_var_operation_name
- _var_subclasses.append(
- VarSubclassEntry(
- cls,
- ToVarOperation,
- python_types,
- is_subclass if not isinstance(is_subclass, types.Unset) else None,
- )
- )
- def __post_init__(self):
- """Post-initialize the var."""
- # Decode any inline Var markup and apply it to the instance
- _var_data, _js_expr = _decode_var_immutable(self._js_expr)
- if _var_data or _js_expr != self._js_expr:
- self.__init__(
- **{
- **dataclasses.asdict(self),
- "_js_expr": _js_expr,
- "_var_data": VarData.merge(self._var_data, _var_data),
- }
- )
- def __hash__(self) -> int:
- """Define a hash function for the var.
- Returns:
- The hash of the var.
- """
- return hash((self._js_expr, self._var_type, self._var_data))
- def _get_all_var_data(self) -> VarData | None:
- """Get all VarData associated with the Var.
- Returns:
- The VarData of the components and all of its children.
- """
- return self._var_data
- def equals(self, other: Var) -> bool:
- """Check if two vars are equal.
- Args:
- other: The other var to compare.
- Returns:
- Whether the vars are equal.
- """
- return (
- self._js_expr == other._js_expr
- and self._var_type == other._var_type
- and self._get_all_var_data() == other._get_all_var_data()
- )
- @overload
- def _replace(
- self,
- _var_type: Type[OTHER_VAR_TYPE],
- merge_var_data: VarData | None = None,
- **kwargs: Any,
- ) -> Var[OTHER_VAR_TYPE]: ...
- @overload
- def _replace(
- self,
- _var_type: GenericType | None = None,
- merge_var_data: VarData | None = None,
- **kwargs: Any,
- ) -> Self: ...
- def _replace(
- self,
- _var_type: GenericType | None = None,
- merge_var_data: VarData | None = None,
- **kwargs: Any,
- ) -> Self | Var:
- """Make a copy of this Var with updated fields.
- Args:
- _var_type: The new type of the Var.
- merge_var_data: VarData to merge into the existing VarData.
- **kwargs: Var fields to update.
- Returns:
- A new Var with the updated fields overwriting the corresponding fields in this Var.
- Raises:
- TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None.
- """
- if kwargs.get("_var_is_local", False) is not False:
- raise TypeError("The _var_is_local argument is not supported for Var.")
- if kwargs.get("_var_is_string", False) is not False:
- raise TypeError("The _var_is_string argument is not supported for Var.")
- if kwargs.get("_var_full_name_needs_state_prefix", False) is not False:
- raise TypeError(
- "The _var_full_name_needs_state_prefix argument is not supported for Var."
- )
- value_with_replaced = dataclasses.replace(
- self,
- _var_type=_var_type or self._var_type,
- _var_data=VarData.merge(
- kwargs.get("_var_data", self._var_data), merge_var_data
- ),
- **kwargs,
- )
- if (js_expr := kwargs.get("_js_expr")) is not None:
- object.__setattr__(value_with_replaced, "_js_expr", js_expr)
- return value_with_replaced
- @overload
- @classmethod
- def create( # pyright: ignore[reportOverlappingOverload]
- cls,
- value: NoReturn,
- _var_data: VarData | None = None,
- ) -> Var[Any]: ...
- @overload
- @classmethod
- def create( # pyright: ignore[reportOverlappingOverload]
- cls,
- value: bool,
- _var_data: VarData | None = None,
- ) -> LiteralBooleanVar: ...
- @overload
- @classmethod
- def create(
- cls,
- value: int,
- _var_data: VarData | None = None,
- ) -> LiteralNumberVar[int]: ...
- @overload
- @classmethod
- def create(
- cls,
- value: float,
- _var_data: VarData | None = None,
- ) -> LiteralNumberVar[float]: ...
- @overload
- @classmethod
- def create( # pyright: ignore [reportOverlappingOverload]
- cls,
- value: str,
- _var_data: VarData | None = None,
- ) -> LiteralStringVar: ...
- @overload
- @classmethod
- def create( # pyright: ignore [reportOverlappingOverload]
- cls,
- value: STRING_T,
- _var_data: VarData | None = None,
- ) -> StringVar[STRING_T]: ...
- @overload
- @classmethod
- def create( # pyright: ignore[reportOverlappingOverload]
- cls,
- value: None,
- _var_data: VarData | None = None,
- ) -> LiteralNoneVar: ...
- @overload
- @classmethod
- def create(
- cls,
- value: MAPPING_TYPE,
- _var_data: VarData | None = None,
- ) -> LiteralObjectVar[MAPPING_TYPE]: ...
- @overload
- @classmethod
- def create(
- cls,
- value: SEQUENCE_TYPE,
- _var_data: VarData | None = None,
- ) -> LiteralArrayVar[SEQUENCE_TYPE]: ...
- @overload
- @classmethod
- def create(
- cls,
- value: OTHER_VAR_TYPE,
- _var_data: VarData | None = None,
- ) -> Var[OTHER_VAR_TYPE]: ...
- @classmethod
- def create(
- cls,
- value: OTHER_VAR_TYPE,
- _var_data: VarData | None = None,
- ) -> Var[OTHER_VAR_TYPE]:
- """Create a var from a value.
- Args:
- value: The value to create the var from.
- _var_data: Additional hooks and imports associated with the Var.
- Returns:
- The var.
- """
- # If the value is already a var, do nothing.
- if isinstance(value, Var):
- return value
- return LiteralVar.create(value, _var_data=_var_data)
- @classmethod
- @deprecated("Use `.create()` instead.")
- def create_safe(
- cls,
- *args: Any,
- **kwargs: Any,
- ) -> Var:
- """Create a var from a value.
- Args:
- *args: The arguments to create the var from.
- **kwargs: The keyword arguments to create the var from.
- Returns:
- The var.
- """
- return cls.create(*args, **kwargs)
- def __format__(self, format_spec: str) -> str:
- """Format the var into a Javascript equivalent to an f-string.
- Args:
- format_spec: The format specifier (Ignored for now).
- Returns:
- The formatted var.
- """
- hashed_var = hash(self)
- _global_vars[hashed_var] = self
- # Encode the _var_data into the formatted output for tracking purposes.
- return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._js_expr}"
- @overload
- def to(self, output: Type[bool]) -> BooleanVar: ... # pyright: ignore [reportOverlappingOverload]
- @overload
- def to(self, output: Type[int]) -> NumberVar[int]: ...
- @overload
- def to(self, output: type[float]) -> NumberVar[float]: ...
- @overload
- def to(self, output: Type[str]) -> StringVar: ... # pyright: ignore [reportOverlappingOverload]
- @overload
- def to(
- self,
- output: type[Sequence[VALUE]] | type[set[VALUE]],
- ) -> ArrayVar[Sequence[VALUE]]: ...
- @overload
- def to(
- self,
- output: type[MAPPING_TYPE],
- ) -> ObjectVar[MAPPING_TYPE]: ...
- @overload
- def to(
- self, output: Type[ObjectVar], var_type: Type[VAR_INSIDE]
- ) -> ObjectVar[VAR_INSIDE]: ...
- @overload
- def to(
- self, output: Type[ObjectVar], var_type: None = None
- ) -> ObjectVar[VAR_TYPE]: ...
- @overload
- def to(self, output: VAR_SUBCLASS, var_type: None = None) -> VAR_SUBCLASS: ...
- @overload
- def to(
- self,
- output: Type[OUTPUT] | types.GenericType,
- var_type: types.GenericType | None = None,
- ) -> OUTPUT: ...
- def to(
- self,
- output: Type[OUTPUT] | types.GenericType,
- var_type: types.GenericType | None = None,
- ) -> Var:
- """Convert the var to a different type.
- Args:
- output: The output type.
- var_type: The type of the var.
- Returns:
- The converted var.
- """
- from .object import ObjectVar
- fixed_output_type = get_origin(output) or output
- # If the first argument is a python type, we map it to the corresponding Var type.
- for var_subclass in _var_subclasses[::-1]:
- if (
- var_subclass.python_types
- and safe_issubclass(fixed_output_type, var_subclass.python_types)
- ) or (
- var_subclass.is_subclass and var_subclass.is_subclass(fixed_output_type)
- ):
- return self.to(var_subclass.var_subclass, output)
- if fixed_output_type is None:
- return get_to_operation(NoneVar).create(self) # pyright: ignore [reportReturnType]
- # Handle fixed_output_type being Base or a dataclass.
- if can_use_in_object_var(output):
- return self.to(ObjectVar, output)
- if inspect.isclass(output):
- for var_subclass in _var_subclasses[::-1]:
- if issubclass(output, var_subclass.var_subclass):
- current_var_type = self._var_type
- if current_var_type is Any:
- new_var_type = var_type
- else:
- new_var_type = var_type or current_var_type
- to_operation_return = var_subclass.to_var_subclass.create(
- value=self, _var_type=new_var_type
- )
- return to_operation_return # pyright: ignore [reportReturnType]
- # If we can't determine the first argument, we just replace the _var_type.
- if not issubclass(output, Var) or var_type is None:
- return dataclasses.replace(
- self,
- _var_type=output,
- )
- # We couldn't determine the output type to be any other Var type, so we replace the _var_type.
- if var_type is not None:
- return dataclasses.replace(
- self,
- _var_type=var_type,
- )
- return self
- # We use `NoReturn` here to catch `Var[Any]` and `Var[Unknown]` cases first.
- @overload
- def guess_type(self: Var[NoReturn]) -> Var: ... # pyright: ignore [reportOverlappingOverload]
- @overload
- def guess_type(self: Var[bool]) -> BooleanVar: ...
- @overload
- def guess_type(self: Var[INT_OR_FLOAT]) -> NumberVar[INT_OR_FLOAT]: ...
- @overload
- def guess_type(self: Var[str]) -> StringVar: ... # pyright: ignore [reportOverlappingOverload]
- @overload
- def guess_type(self: Var[Sequence[VALUE]]) -> ArrayVar[Sequence[VALUE]]: ...
- @overload
- def guess_type(self: Var[Set[VALUE]]) -> ArrayVar[Set[VALUE]]: ...
- @overload
- def guess_type(
- self: Var[Dict[VALUE, OTHER_VAR_TYPE]],
- ) -> ObjectVar[Dict[VALUE, OTHER_VAR_TYPE]]: ...
- @overload
- def guess_type(self: Var[BASE_TYPE]) -> ObjectVar[BASE_TYPE]: ...
- @overload
- def guess_type(self) -> Self: ...
- def guess_type(self) -> Var:
- """Guesses the type of the variable based on its `_var_type` attribute.
- Returns:
- Var: The guessed type of the variable.
- Raises:
- TypeError: If the type is not supported for guessing.
- """
- from .object import ObjectVar
- var_type = self._var_type
- if var_type is None:
- return self.to(None)
- if types.is_optional(var_type):
- var_type = types.get_args(var_type)[0]
- if var_type is Any:
- return self
- fixed_type = get_origin(var_type) or var_type
- if fixed_type in types.UnionTypes:
- inner_types = get_args(var_type)
- for var_subclass in _var_subclasses:
- if all(
- (
- safe_issubclass(t, var_subclass.python_types)
- or (var_subclass.is_subclass and var_subclass.is_subclass(t))
- )
- for t in inner_types
- ):
- return self.to(var_subclass.var_subclass, self._var_type)
- if can_use_in_object_var(var_type):
- return self.to(ObjectVar, self._var_type)
- return self
- if fixed_type is Literal:
- args = get_args(var_type)
- fixed_type = unionize(*(type(arg) for arg in args))
- if not inspect.isclass(fixed_type):
- raise TypeError(f"Unsupported type {var_type} for guess_type.")
- if fixed_type is None:
- return self.to(None)
- for var_subclass in _var_subclasses[::-1]:
- if safe_issubclass(fixed_type, var_subclass.python_types) or (
- var_subclass.is_subclass and var_subclass.is_subclass(fixed_type)
- ):
- return self.to(var_subclass.var_subclass, self._var_type)
- if can_use_in_object_var(fixed_type):
- return self.to(ObjectVar, self._var_type)
- return self
- def _get_default_value(self) -> Any:
- """Get the default value of the var.
- Returns:
- The default value of the var.
- Raises:
- ImportError: If the var is a dataframe and pandas is not installed.
- """
- if types.is_optional(self._var_type):
- return None
- type_ = (
- get_origin(self._var_type)
- if types.is_generic_alias(self._var_type)
- else self._var_type
- )
- if type_ is Literal:
- args = get_args(self._var_type)
- return args[0] if args else None
- if issubclass(type_, str):
- return ""
- if issubclass(type_, types.get_args(Union[int, float])):
- return 0
- if issubclass(type_, bool):
- return False
- if issubclass(type_, list):
- return []
- if issubclass(type_, Mapping):
- return {}
- if issubclass(type_, tuple):
- return ()
- if types.is_dataframe(type_):
- try:
- import pandas as pd
- return pd.DataFrame()
- except ImportError as e:
- raise ImportError(
- "Please install pandas to use dataframes in your app."
- ) from e
- return set() if issubclass(type_, set) else None
- def _get_setter_name(self, include_state: bool = True) -> str:
- """Get the name of the var's generated setter function.
- Args:
- include_state: Whether to include the state name in the setter name.
- Returns:
- The name of the setter function.
- """
- setter = constants.SETTER_PREFIX + self._var_field_name
- var_data = self._get_all_var_data()
- if var_data is None:
- return setter
- if not include_state or var_data.state == "":
- return setter
- return ".".join((var_data.state, setter))
- def _get_setter(self) -> Callable[[BaseState, Any], None]:
- """Get the var's setter function.
- Returns:
- A function that that creates a setter for the var.
- """
- actual_name = self._var_field_name
- def setter(state: Any, value: Any):
- """Get the setter for the var.
- Args:
- state: The state within which we add the setter function.
- value: The value to set.
- """
- if self._var_type in [int, float]:
- try:
- value = self._var_type(value)
- setattr(state, actual_name, value)
- except ValueError:
- console.debug(
- f"{type(state).__name__}.{self._js_expr}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.",
- )
- else:
- setattr(state, actual_name, value)
- setter.__annotations__["value"] = self._var_type
- setter.__qualname__ = self._get_setter_name()
- return setter
- def _var_set_state(self, state: type[BaseState] | str) -> Self:
- """Set the state of the var.
- Args:
- state: The state to set.
- Returns:
- The var with the state set.
- """
- formatted_state_name = (
- state
- if isinstance(state, str)
- else format_state_name(state.get_full_name())
- )
- return StateOperation.create( # pyright: ignore [reportReturnType]
- formatted_state_name,
- self,
- _var_data=VarData.merge(
- VarData.from_state(state, self._js_expr), self._var_data
- ),
- ).guess_type()
- def __eq__(self, other: Var | Any) -> BooleanVar:
- """Check if the current variable is equal to the given variable.
- Args:
- other (Var | Any): The variable to compare with.
- Returns:
- BooleanVar: A BooleanVar object representing the result of the equality check.
- """
- from .number import equal_operation
- return equal_operation(self, other).guess_type()
- def __ne__(self, other: Var | Any) -> BooleanVar:
- """Check if the current object is not equal to the given object.
- Parameters:
- other (Var | Any): The object to compare with.
- Returns:
- BooleanVar: A BooleanVar object representing the result of the comparison.
- """
- from .number import equal_operation
- return (~equal_operation(self, other)).guess_type()
- def bool(self) -> BooleanVar:
- """Convert the var to a boolean.
- Returns:
- The boolean var.
- """
- from .number import boolify
- return boolify(self) # pyright: ignore [reportReturnType]
- def __and__(self, other: Var | Any) -> Var:
- """Perform a logical AND operation on the current instance and another variable.
- Args:
- other: The variable to perform the logical AND operation with.
- Returns:
- A `BooleanVar` object representing the result of the logical AND operation.
- """
- return and_operation(self, other)
- def __rand__(self, other: Var | Any) -> Var:
- """Perform a logical AND operation on the current instance and another variable.
- Args:
- other: The variable to perform the logical AND operation with.
- Returns:
- A `BooleanVar` object representing the result of the logical AND operation.
- """
- return and_operation(other, self)
- def __or__(self, other: Var | Any) -> Var:
- """Perform a logical OR operation on the current instance and another variable.
- Args:
- other: The variable to perform the logical OR operation with.
- Returns:
- A `BooleanVar` object representing the result of the logical OR operation.
- """
- return or_operation(self, other)
- def __ror__(self, other: Var | Any) -> Var:
- """Perform a logical OR operation on the current instance and another variable.
- Args:
- other: The variable to perform the logical OR operation with.
- Returns:
- A `BooleanVar` object representing the result of the logical OR operation.
- """
- return or_operation(other, self)
- def __invert__(self) -> BooleanVar:
- """Perform a logical NOT operation on the current instance.
- Returns:
- A `BooleanVar` object representing the result of the logical NOT operation.
- """
- return (~self.bool()).guess_type()
- def to_string(self, use_json: bool = True) -> StringVar:
- """Convert the var to a string.
- Args:
- use_json: Whether to use JSON stringify. If False, uses Object.prototype.toString.
- Returns:
- The string var.
- """
- from .function import JSON_STRINGIFY, PROTOTYPE_TO_STRING
- from .sequence import StringVar
- return (
- JSON_STRINGIFY.call(self).to(StringVar)
- if use_json
- else PROTOTYPE_TO_STRING.call(self).to(StringVar)
- )
- def _as_ref(self) -> Var:
- """Get a reference to the var.
- Returns:
- The reference to the var.
- """
- from .object import ObjectVar
- refs = Var(
- _js_expr="refs",
- _var_data=VarData(
- imports={
- f"$/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
- }
- ),
- ).to(ObjectVar, Mapping[str, str])
- return refs[LiteralVar.create(str(self))]
- @deprecated("Use `.js_type()` instead.")
- def _type(self) -> StringVar:
- """Returns the type of the object.
- This method uses the `typeof` function from the `FunctionStringVar` class
- to determine the type of the object.
- Returns:
- StringVar: A string variable representing the type of the object.
- """
- return self.js_type()
- def js_type(self) -> StringVar:
- """Returns the javascript type of the object.
- This method uses the `typeof` function from the `FunctionStringVar` class
- to determine the type of the object.
- Returns:
- StringVar: A string variable representing the type of the object.
- """
- from .function import FunctionStringVar
- from .sequence import StringVar
- type_of = FunctionStringVar("typeof")
- return type_of.call(self).to(StringVar)
- def _without_data(self):
- """Create a copy of the var without the data.
- Returns:
- The var without the data.
- """
- return dataclasses.replace(self, _var_data=None)
- def __get__(self, instance: Any, owner: Any):
- """Get the var.
- Args:
- instance: The instance to get the var from.
- owner: The owner of the var.
- Returns:
- The var.
- """
- return self
- def _decode(self) -> Any:
- """Decode Var as a python value.
- Note that Var with state set cannot be decoded python-side and will be
- returned as full_name.
- Returns:
- The decoded value or the Var name.
- """
- if isinstance(self, LiteralVar):
- return self._var_value
- try:
- return json.loads(str(self))
- except ValueError:
- return str(self)
- @property
- def _var_state(self) -> str:
- """Compat method for getting the state.
- Returns:
- The state name associated with the var.
- """
- var_data = self._get_all_var_data()
- return var_data.state if var_data else ""
- @overload
- @classmethod
- def range(cls, stop: int | NumberVar, /) -> ArrayVar[Sequence[int]]: ...
- @overload
- @classmethod
- def range(
- cls,
- start: int | NumberVar,
- end: int | NumberVar,
- step: int | NumberVar = 1,
- /,
- ) -> ArrayVar[Sequence[int]]: ...
- @classmethod
- def range(
- cls,
- first_endpoint: int | Var[int],
- second_endpoint: int | Var[int] | None = None,
- step: int | Var[int] | None = None,
- /,
- ) -> ArrayVar[Sequence[int]]:
- """Create a range of numbers.
- Args:
- first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range.
- second_endpoint: The end of the range.
- step: The step of the range.
- Returns:
- The range of numbers.
- """
- from .sequence import ArrayVar
- if step is None:
- return ArrayVar.range(first_endpoint, second_endpoint)
- return ArrayVar.range(first_endpoint, second_endpoint, step)
- if not TYPE_CHECKING:
- def __bool__(self) -> bool:
- """Raise exception if using Var in a boolean context.
- Raises:
- VarTypeError: when attempting to bool-ify the Var.
- # noqa: DAR101 self
- """
- raise VarTypeError(
- f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. "
- "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
- )
- def __iter__(self) -> Any:
- """Raise exception if using Var in an iterable context.
- Raises:
- VarTypeError: when attempting to iterate over the Var.
- # noqa: DAR101 self
- """
- raise VarTypeError(
- f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`."
- )
- def __contains__(self, _: Any) -> Var:
- """Override the 'in' operator to alert the user that it is not supported.
- Raises:
- VarTypeError: the operation is not supported
- # noqa: DAR101 self
- """
- raise VarTypeError(
- "'in' operator not supported for Var types, use Var.contains() instead."
- )
- OUTPUT = TypeVar("OUTPUT", bound=Var)
- VAR_SUBCLASS = TypeVar("VAR_SUBCLASS", bound=Var)
- VAR_INSIDE = TypeVar("VAR_INSIDE")
- class VarWithDefault(Var[VAR_TYPE]):
- """Annotate an optional argument."""
- def __init__(self, default_value: VAR_TYPE):
- """Initialize the default value.
- Args:
- default_value: The default value.
- """
- super().__init__("")
- self._default = default_value
- @property
- def default(self) -> Var[VAR_TYPE]:
- """Get the default value.
- Returns:
- The default value.
- """
- return Var.create(self._default)
- class ToOperation:
- """A var operation that converts a var to another type."""
- def __getattr__(self, name: str) -> Any:
- """Get an attribute of the var.
- Args:
- name: The name of the attribute.
- Returns:
- The attribute of the var.
- """
- from .object import ObjectVar
- if isinstance(self, ObjectVar) and name != "_js_expr":
- return ObjectVar.__getattr__(self, name)
- return getattr(self._original, name)
- def __post_init__(self):
- """Post initialization."""
- object.__delattr__(self, "_js_expr")
- def __hash__(self) -> int:
- """Calculate the hash value of the object.
- Returns:
- int: The hash value of the object.
- """
- return hash(self._original)
- def _get_all_var_data(self) -> VarData | None:
- """Get all the var data.
- Returns:
- The var data.
- """
- return VarData.merge(
- self._original._get_all_var_data(),
- self._var_data,
- )
- @classmethod
- def create(
- cls,
- value: Var,
- _var_type: GenericType | None = None,
- _var_data: VarData | None = None,
- ):
- """Create a ToOperation.
- Args:
- value: The value of the var.
- _var_type: The type of the Var.
- _var_data: Additional hooks and imports associated with the Var.
- Returns:
- The ToOperation.
- """
- return cls(
- _js_expr="", # pyright: ignore [reportCallIssue]
- _var_data=_var_data, # pyright: ignore [reportCallIssue]
- _var_type=_var_type or cls._default_var_type, # pyright: ignore [reportCallIssue, reportAttributeAccessIssue]
- _original=value, # pyright: ignore [reportCallIssue]
- )
- class LiteralVar(Var):
- """Base class for immutable literal vars."""
- def __init_subclass__(cls, **kwargs):
- """Initialize the subclass.
- Args:
- **kwargs: Additional keyword arguments.
- Raises:
- TypeError: If the LiteralVar subclass does not have a corresponding Var subclass.
- """
- super().__init_subclass__(**kwargs)
- bases = cls.__bases__
- bases_normalized = [
- base if inspect.isclass(base) else get_origin(base) for base in bases
- ]
- possible_bases = [
- base
- for base in bases_normalized
- if issubclass(base, Var) and base != LiteralVar
- ]
- if not possible_bases:
- raise TypeError(
- f"LiteralVar subclass {cls} must have a base class that is a subclass of Var and not LiteralVar."
- )
- var_subclasses = [
- var_subclass
- for var_subclass in _var_subclasses
- if var_subclass.var_subclass in possible_bases
- ]
- if not var_subclasses:
- raise TypeError(
- f"LiteralVar {cls} must have a base class annotated with `python_types`."
- )
- if len(var_subclasses) != 1:
- raise TypeError(
- f"LiteralVar {cls} must have exactly one base class annotated with `python_types`."
- )
- var_subclass = var_subclasses[0]
- # Remove the old subclass, happens because __init_subclass__ is called twice
- # for each subclass. This is because of __slots__ in dataclasses.
- for var_literal_subclass in list(_var_literal_subclasses):
- if var_literal_subclass[1] is var_subclass:
- _var_literal_subclasses.remove(var_literal_subclass)
- _var_literal_subclasses.append((cls, var_subclass))
- @classmethod
- def create( # pyright: ignore [reportArgumentType]
- cls,
- value: Any,
- _var_data: VarData | None = None,
- ) -> Var:
- """Create a var from a value.
- Args:
- value: The value to create the var from.
- _var_data: Additional hooks and imports associated with the Var.
- Returns:
- The var.
- Raises:
- TypeError: If the value is not a supported type for LiteralVar.
- """
- if isinstance(value, Var):
- if _var_data is None:
- return value
- return value._replace(merge_var_data=_var_data)
- for literal_subclass, var_subclass in _var_literal_subclasses[::-1]:
- if isinstance(value, var_subclass.python_types):
- return literal_subclass.create(value, _var_data=_var_data)
- from reflex.event import EventHandler
- from reflex.utils.format import get_event_handler_parts
- from .object import LiteralObjectVar
- from .sequence import LiteralStringVar
- if isinstance(value, EventHandler):
- return Var(_js_expr=".".join(filter(None, get_event_handler_parts(value))))
- serialized_value = serializers.serialize(value)
- if serialized_value is not None:
- if isinstance(serialized_value, Mapping):
- return LiteralObjectVar.create(
- serialized_value,
- _var_type=type(value),
- _var_data=_var_data,
- )
- if isinstance(serialized_value, str):
- return LiteralStringVar.create(
- serialized_value, _var_type=type(value), _var_data=_var_data
- )
- return LiteralVar.create(serialized_value, _var_data=_var_data)
- if isinstance(value, Base):
- # get the fields of the pydantic class
- fields = value.__fields__.keys()
- one_level_dict = {field: getattr(value, field) for field in fields}
- return LiteralObjectVar.create(
- {
- field: value
- for field, value in one_level_dict.items()
- if not callable(value)
- },
- _var_type=type(value),
- _var_data=_var_data,
- )
- if dataclasses.is_dataclass(value) and not isinstance(value, type):
- return LiteralObjectVar.create(
- {
- k: (None if callable(v) else v)
- for k, v in dataclasses.asdict(value).items()
- },
- _var_type=type(value),
- _var_data=_var_data,
- )
- if isinstance(value, range):
- return ArrayVar.range(value.start, value.stop, value.step)
- raise TypeError(
- f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}."
- )
- def __post_init__(self):
- """Post-initialize the var."""
- @property
- def _var_value(self) -> Any:
- raise NotImplementedError(
- "LiteralVar subclasses must implement the _var_value property."
- )
- def json(self) -> str:
- """Serialize the var to a JSON string.
- Raises:
- NotImplementedError: If the method is not implemented.
- """
- raise NotImplementedError(
- "LiteralVar subclasses must implement the json method."
- )
- @serializers.serializer
- def serialize_literal(value: LiteralVar):
- """Serialize a Literal type.
- Args:
- value: The Literal to serialize.
- Returns:
- The serialized Literal.
- """
- return value._var_value
- def get_python_literal(value: Union[LiteralVar, Any]) -> Any | None:
- """Get the Python literal value.
- Args:
- value: The value to get the Python literal value of.
- Returns:
- The Python literal value.
- """
- if isinstance(value, LiteralVar):
- return value._var_value
- if isinstance(value, Var):
- return None
- return value
- def validate_arg(type_hint: GenericType) -> Callable[[Any], str | None]:
- """Create a validator for an argument.
- Args:
- type_hint: The type hint of the argument.
- Returns:
- The validator.
- """
- def validate(value: Any):
- if isinstance(value, LiteralVar):
- if not _isinstance(value._var_value, type_hint):
- return f"Expected {type_hint} but got {value._var_value} of type {type(value._var_value)}."
- elif isinstance(value, Var):
- if not typehint_issubclass(value._var_type, type_hint):
- return f"Expected {type_hint} but got {value._var_type}."
- else:
- if not _isinstance(value, type_hint):
- return f"Expected {type_hint} but got {value} of type {type(value)}."
- return validate
- P = ParamSpec("P")
- T = TypeVar("T")
- V1 = TypeVar("V1")
- V2 = TypeVar("V2")
- V3 = TypeVar("V3")
- V4 = TypeVar("V4")
- V5 = TypeVar("V5")
- class TypeComputer(Protocol):
- """A protocol for type computers."""
- def __call__(self, *args: Var) -> Tuple[GenericType, Union[TypeComputer, None]]:
- """Compute the type of the operation.
- Args:
- *args: The arguments to compute the type of.
- """
- ...
- @overload
- def var_operation(
- func: Callable[[Var[V1], Var[V2], Var[V3]], CustomVarOperationReturn[T]],
- ) -> ArgsFunctionOperation[ReflexCallable[[V1, V2, V3], T]]: ...
- @overload
- def var_operation(
- func: Callable[[Var[V1], Var[V2], VarWithDefault[V3]], CustomVarOperationReturn[T]],
- ) -> ArgsFunctionOperation[ReflexCallable[[V1, V2, VarWithDefault[V3]], T]]: ...
- @overload
- def var_operation(
- func: Callable[
- [
- Var[V1],
- VarWithDefault[V2],
- VarWithDefault[V3],
- ],
- CustomVarOperationReturn[T],
- ],
- ) -> ArgsFunctionOperation[
- ReflexCallable[
- [
- V1,
- VarWithDefault[V2],
- VarWithDefault[V3],
- ],
- T,
- ]
- ]: ...
- @overload
- def var_operation(
- func: Callable[
- [
- VarWithDefault[V1],
- VarWithDefault[V2],
- VarWithDefault[V3],
- ],
- CustomVarOperationReturn[T],
- ],
- ) -> ArgsFunctionOperation[
- ReflexCallable[
- [
- VarWithDefault[V1],
- VarWithDefault[V1],
- VarWithDefault[V1],
- ],
- T,
- ]
- ]: ...
- @overload
- def var_operation(
- func: Callable[[Var[V1], Var[V2]], CustomVarOperationReturn[T]],
- ) -> ArgsFunctionOperation[ReflexCallable[[V1, V2], T]]: ...
- @overload
- def var_operation(
- func: Callable[
- [
- Var[V1],
- VarWithDefault[V2],
- ],
- CustomVarOperationReturn[T],
- ],
- ) -> ArgsFunctionOperation[
- ReflexCallable[
- [
- V1,
- VarWithDefault[V2],
- ],
- T,
- ]
- ]: ...
- @overload
- def var_operation(
- func: Callable[
- [
- VarWithDefault[V1],
- VarWithDefault[V2],
- ],
- CustomVarOperationReturn[T],
- ],
- ) -> ArgsFunctionOperation[
- ReflexCallable[
- [
- VarWithDefault[V1],
- VarWithDefault[V2],
- ],
- T,
- ]
- ]: ...
- @overload
- def var_operation(
- func: Callable[[Var[V1]], CustomVarOperationReturn[T]],
- ) -> ArgsFunctionOperation[ReflexCallable[[V1], T]]: ...
- @overload
- def var_operation(
- func: Callable[
- [VarWithDefault[V1]],
- CustomVarOperationReturn[T],
- ],
- ) -> ArgsFunctionOperation[
- ReflexCallable[
- [VarWithDefault[V1]],
- T,
- ]
- ]: ...
- @overload
- def var_operation(
- func: Callable[[], CustomVarOperationReturn[T]],
- ) -> ArgsFunctionOperation[ReflexCallable[[], T]]: ...
- def var_operation(
- func: Callable[..., CustomVarOperationReturn[T]],
- ) -> ArgsFunctionOperation[ReflexCallable[..., T]]:
- """Decorator for creating a var operation.
- Example:
- ```python
- @var_operation
- def add(a: Var[int], b: Var[int]):
- return var_operation_return(f"{a} + {b}")
- ```
- Args:
- func: The function to decorate.
- Returns:
- The decorated function.
- Raises:
- TypeError: If the function has keyword-only arguments or arguments without Var type hints.
- """
- from .function import ArgsFunctionOperation, ReflexCallable
- func_name = func.__name__
- func_arg_spec = inspect.getfullargspec(func)
- func_signature = inspect.signature(func)
- if func_arg_spec.kwonlyargs:
- raise TypeError(f"Function {func_name} cannot have keyword-only arguments.")
- if func_arg_spec.varargs:
- raise TypeError(f"Function {func_name} cannot have variable arguments.")
- arg_names = func_arg_spec.args
- arg_default_values: Sequence[inspect.Parameter.empty | VarWithDefault] = tuple(
- (
- default_value
- if isinstance(
- (default_value := func_signature.parameters[arg_name].default),
- VarWithDefault,
- )
- else inspect.Parameter.empty()
- )
- for arg_name in arg_names
- )
- type_hints = get_type_hints(func)
- if not all(
- (get_origin((type_hint := type_hints.get(arg_name, Any))) or type_hint)
- in (Var, VarWithDefault)
- and len(get_args(type_hint)) <= 1
- for arg_name in arg_names
- ):
- raise TypeError(
- f"Function {func_name} must have type hints of the form `Var[Type]`."
- )
- args_with_type_hints = tuple(
- (arg_name, (args[0] if (args := get_args(type_hints[arg_name])) else Any))
- for arg_name in arg_names
- )
- arg_vars = tuple(
- (
- Var("_" + arg_name, _var_type=arg_python_type)
- if not isinstance(arg_python_type, TypeVar)
- else Var("_" + arg_name)
- )
- for arg_name, arg_python_type in args_with_type_hints
- )
- custom_operation_return = func(*arg_vars)
- def simplified_operation(*args):
- return func(*args)._js_expr
- args_operation = ArgsFunctionOperation.create(
- tuple(map(str, arg_vars)),
- custom_operation_return,
- default_values=arg_default_values,
- validators=tuple(
- validate_arg(arg_type)
- if not isinstance(arg_type, TypeVar)
- else validate_arg(arg_type.__bound__ or Any)
- for _, arg_type in args_with_type_hints
- ),
- function_name=func_name,
- type_computer=custom_operation_return._type_computer,
- _raw_js_function=custom_operation_return._raw_js_function,
- _original_var_operation=simplified_operation,
- _var_type=ReflexCallable[
- tuple( # pyright: ignore [reportInvalidTypeArguments]
- arg_python_type
- if isinstance(arg_default_values[i], inspect.Parameter)
- else VarWithDefault[arg_python_type]
- for i, (_, arg_python_type) in enumerate(args_with_type_hints)
- ),
- custom_operation_return._var_type,
- ],
- )
- return args_operation
- def figure_out_type(value: Any) -> types.GenericType:
- """Figure out the type of the value.
- Args:
- value: The value to figure out the type of.
- Returns:
- The type of the value.
- """
- if isinstance(value, Var):
- return value._var_type
- type_ = type(value)
- if has_args(type_):
- return type_
- if isinstance(value, list):
- return List[unionize(*(figure_out_type(v) for v in value))]
- if isinstance(value, set):
- return Set[unionize(*(figure_out_type(v) for v in value))]
- if isinstance(value, tuple):
- return Tuple[unionize(*(figure_out_type(v) for v in value)), ...]
- if isinstance(value, Mapping):
- return Mapping[
- unionize(*(figure_out_type(k) for k in value)),
- unionize(*(figure_out_type(v) for v in value.values())),
- ]
- return type(value)
- GLOBAL_CACHE = {}
- class cached_property: # noqa: N801
- """A cached property that caches the result of the function."""
- def __init__(self, func: Callable):
- """Initialize the cached_property.
- Args:
- func: The function to cache.
- """
- self._func = func
- self._attrname = None
- def __set_name__(self, owner: Any, name: str):
- """Set the name of the cached property.
- Args:
- owner: The owner of the cached property.
- name: The name of the cached property.
- Raises:
- TypeError: If the cached property is assigned to two different names.
- """
- if self._attrname is None:
- self._attrname = name
- original_del = getattr(owner, "__del__", None)
- def delete_property(this: Any):
- """Delete the cached property.
- Args:
- this: The object to delete the cached property from.
- """
- cached_field_name = "_reflex_cache_" + name
- try:
- unique_id = object.__getattribute__(this, cached_field_name)
- except AttributeError:
- if original_del is not None:
- original_del(this)
- return
- if unique_id in GLOBAL_CACHE:
- del GLOBAL_CACHE[unique_id]
- if original_del is not None:
- original_del(this)
- owner.__del__ = delete_property
- elif name != self._attrname:
- raise TypeError(
- "Cannot assign the same cached_property to two different names "
- f"({self._attrname!r} and {name!r})."
- )
- def __get__(self, instance: Any, owner: Type | None = None):
- """Get the cached property.
- Args:
- instance: The instance to get the cached property from.
- owner: The owner of the cached property.
- Returns:
- The cached property.
- Raises:
- TypeError: If the class does not have __set_name__.
- """
- if self._attrname is None:
- raise TypeError(
- "Cannot use cached_property on a class without __set_name__."
- )
- cached_field_name = "_reflex_cache_" + self._attrname
- try:
- unique_id = object.__getattribute__(instance, cached_field_name)
- except AttributeError:
- unique_id = uuid.uuid4().int
- object.__setattr__(instance, cached_field_name, unique_id)
- if unique_id not in GLOBAL_CACHE:
- GLOBAL_CACHE[unique_id] = self._func(instance)
- return GLOBAL_CACHE[unique_id]
- cached_property_no_lock = cached_property
- class CachedVarOperation:
- """Base class for cached var operations to lower boilerplate code."""
- def __post_init__(self):
- """Post-initialize the CachedVarOperation."""
- object.__delattr__(self, "_js_expr")
- def __getattr__(self, name: str) -> Any:
- """Get an attribute of the var.
- Args:
- name: The name of the attribute.
- Returns:
- The attribute.
- """
- if name == "_js_expr":
- return self._cached_var_name
- parent_classes = inspect.getmro(type(self))
- next_class = parent_classes[parent_classes.index(CachedVarOperation) + 1]
- return next_class.__getattr__(self, name)
- def _get_all_var_data(self) -> VarData | None:
- """Get all VarData associated with the Var.
- Returns:
- The VarData of the components and all of its children.
- """
- return self._cached_get_all_var_data
- @cached_property_no_lock
- def _cached_get_all_var_data(self) -> VarData | None:
- """Get the cached VarData.
- Returns:
- The cached VarData.
- """
- return VarData.merge(
- *(
- value._get_all_var_data() if isinstance(value, Var) else None
- for value in (
- getattr(self, field.name)
- for field in dataclasses.fields(self) # pyright: ignore [reportArgumentType]
- )
- ),
- self._var_data,
- )
- def __hash__(self) -> int:
- """Calculate the hash of the object.
- Returns:
- The hash of the object.
- """
- return hash(
- (
- type(self).__name__,
- *[
- getattr(self, field.name)
- for field in dataclasses.fields(self) # pyright: ignore [reportArgumentType]
- if field.name not in ["_js_expr", "_var_data", "_var_type"]
- ],
- )
- )
- RETURN_TYPE = TypeVar("RETURN_TYPE")
- class FakeComputedVarBaseClass(property):
- """A fake base class for ComputedVar to avoid inheriting from property."""
- __pydantic_run_validation__ = False
- def is_computed_var(obj: Any) -> TypeGuard[ComputedVar]:
- """Check if the object is a ComputedVar.
- Args:
- obj: The object to check.
- Returns:
- Whether the object is a ComputedVar.
- """
- return isinstance(obj, FakeComputedVarBaseClass)
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- slots=True,
- )
- class ComputedVar(Var[RETURN_TYPE]):
- """A field with computed getters."""
- # Whether to track dependencies and cache computed values
- _cache: bool = dataclasses.field(default=False)
- # Whether the computed var is a backend var
- _backend: bool = dataclasses.field(default=False)
- # The initial value of the computed var
- _initial_value: RETURN_TYPE | types.Unset = dataclasses.field(default=types.Unset())
- # Explicit var dependencies to track
- _static_deps: dict[str | None, set[str]] = dataclasses.field(default_factory=dict)
- # Whether var dependencies should be auto-determined
- _auto_deps: bool = dataclasses.field(default=True)
- # Interval at which the computed var should be updated
- _update_interval: Optional[datetime.timedelta] = dataclasses.field(default=None)
- _fget: Callable[[BaseState], RETURN_TYPE] = dataclasses.field(
- default_factory=lambda: lambda _: None
- ) # pyright: ignore [reportAssignmentType]
- def __init__(
- self,
- fget: Callable[[BASE_STATE], RETURN_TYPE],
- initial_value: RETURN_TYPE | types.Unset = types.Unset(),
- cache: bool = True,
- deps: Optional[List[Union[str, Var]]] = None,
- auto_deps: bool = True,
- interval: Optional[Union[int, datetime.timedelta]] = None,
- backend: bool | None = None,
- **kwargs,
- ):
- """Initialize a ComputedVar.
- Args:
- fget: The getter function.
- initial_value: The initial value of the computed var.
- cache: Whether to cache the computed value.
- deps: Explicit var dependencies to track.
- auto_deps: Whether var dependencies should be auto-determined.
- interval: Interval at which the computed var should be updated.
- backend: Whether the computed var is a backend var.
- **kwargs: additional attributes to set on the instance
- Raises:
- TypeError: If the computed var dependencies are not Var instances or var names.
- UntypedComputedVarError: If the computed var is untyped.
- """
- hint = kwargs.pop("return_type", None) or get_type_hints(fget).get(
- "return", Any
- )
- if hint is Any:
- raise UntypedComputedVarError(var_name=fget.__name__)
- kwargs.setdefault("_js_expr", fget.__name__)
- kwargs.setdefault("_var_type", hint)
- Var.__init__(
- self,
- _js_expr=kwargs.pop("_js_expr"),
- _var_type=kwargs.pop("_var_type"),
- _var_data=kwargs.pop("_var_data", None),
- )
- if kwargs:
- raise TypeError(f"Unexpected keyword arguments: {tuple(kwargs)}")
- if backend is None:
- backend = fget.__name__.startswith("_")
- object.__setattr__(self, "_backend", backend)
- object.__setattr__(self, "_initial_value", initial_value)
- object.__setattr__(self, "_cache", cache)
- if isinstance(interval, int):
- interval = datetime.timedelta(seconds=interval)
- object.__setattr__(self, "_update_interval", interval)
- object.__setattr__(
- self,
- "_static_deps",
- self._calculate_static_deps(deps),
- )
- object.__setattr__(self, "_auto_deps", auto_deps)
- object.__setattr__(self, "_fget", fget)
- def _calculate_static_deps(
- self,
- deps: Union[List[Union[str, Var]], dict[str | None, set[str]]] | None = None,
- ) -> dict[str | None, set[str]]:
- """Calculate the static dependencies of the computed var from user input or existing dependencies.
- Args:
- deps: The user input dependencies or existing dependencies.
- Returns:
- The static dependencies.
- """
- if isinstance(deps, dict):
- # Assume a dict is coming from _replace, so no special processing.
- return deps
- _static_deps = {}
- if deps is not None:
- for dep in deps:
- _static_deps = self._add_static_dep(dep, _static_deps)
- return _static_deps
- def _add_static_dep(
- self, dep: Union[str, Var], deps: dict[str | None, set[str]] | None = None
- ) -> dict[str | None, set[str]]:
- """Add a static dependency to the computed var or existing dependency set.
- Args:
- dep: The dependency to add.
- deps: The existing dependency set.
- Returns:
- The updated dependency set.
- Raises:
- TypeError: If the computed var dependencies are not Var instances or var names.
- """
- if deps is None:
- deps = self._static_deps
- if isinstance(dep, Var):
- state_name = (
- all_var_data.state
- if (all_var_data := dep._get_all_var_data()) and all_var_data.state
- else None
- )
- if all_var_data is not None:
- var_name = all_var_data.field_name
- else:
- var_name = dep._js_expr
- deps.setdefault(state_name, set()).add(var_name)
- elif isinstance(dep, str) and dep != "":
- deps.setdefault(None, set()).add(dep)
- else:
- raise TypeError(
- "ComputedVar dependencies must be Var instances or var names (non-empty strings)."
- )
- return deps
- @override
- def _replace(
- self,
- merge_var_data: VarData | None = None,
- **kwargs: Any,
- ) -> Self:
- """Replace the attributes of the ComputedVar.
- Args:
- merge_var_data: VarData to merge into the existing VarData.
- **kwargs: Var fields to update.
- Returns:
- The new ComputedVar instance.
- Raises:
- TypeError: If kwargs contains keys that are not allowed.
- """
- if "deps" in kwargs:
- kwargs["deps"] = self._calculate_static_deps(kwargs["deps"])
- field_values = {
- "fget": kwargs.pop("fget", self._fget),
- "initial_value": kwargs.pop("initial_value", self._initial_value),
- "cache": kwargs.pop("cache", self._cache),
- "deps": kwargs.pop("deps", self._static_deps),
- "auto_deps": kwargs.pop("auto_deps", self._auto_deps),
- "interval": kwargs.pop("interval", self._update_interval),
- "backend": kwargs.pop("backend", self._backend),
- "_js_expr": kwargs.pop("_js_expr", self._js_expr),
- "_var_type": kwargs.pop("_var_type", self._var_type),
- "_var_data": kwargs.pop(
- "_var_data", VarData.merge(self._var_data, merge_var_data)
- ),
- "return_type": kwargs.pop("return_type", self._var_type),
- }
- if kwargs:
- unexpected_kwargs = ", ".join(kwargs.keys())
- raise TypeError(f"Unexpected keyword arguments: {unexpected_kwargs}")
- return type(self)(**field_values)
- @property
- def _cache_attr(self) -> str:
- """Get the attribute used to cache the value on the instance.
- Returns:
- An attribute name.
- """
- return f"__cached_{self._js_expr}"
- @property
- def _last_updated_attr(self) -> str:
- """Get the attribute used to store the last updated timestamp.
- Returns:
- An attribute name.
- """
- return f"__last_updated_{self._js_expr}"
- def needs_update(self, instance: BaseState) -> bool:
- """Check if the computed var needs to be updated.
- Args:
- instance: The state instance that the computed var is attached to.
- Returns:
- True if the computed var needs to be updated, False otherwise.
- """
- if self._update_interval is None:
- return False
- last_updated = getattr(instance, self._last_updated_attr, None)
- if last_updated is None:
- return True
- return datetime.datetime.now() - last_updated > self._update_interval
- @overload
- def __get__(
- self: ComputedVar[bool],
- instance: None,
- owner: Type,
- ) -> BooleanVar: ...
- @overload
- def __get__(
- self: ComputedVar[int] | ComputedVar[float],
- instance: None,
- owner: Type,
- ) -> NumberVar: ...
- @overload
- def __get__(
- self: ComputedVar[str],
- instance: None,
- owner: Type,
- ) -> StringVar: ...
- @overload
- def __get__(
- self: ComputedVar[MAPPING_TYPE],
- instance: None,
- owner: Type,
- ) -> ObjectVar[MAPPING_TYPE]: ...
- @overload
- def __get__(
- self: ComputedVar[SEQUENCE_TYPE],
- instance: None,
- owner: Type,
- ) -> ArrayVar[SEQUENCE_TYPE]: ...
- @overload
- def __get__(self, instance: None, owner: Type) -> ComputedVar[RETURN_TYPE]: ...
- @overload
- def __get__(self, instance: BaseState, owner: Type) -> RETURN_TYPE: ...
- def __get__(self, instance: BaseState | None, owner: Type):
- """Get the ComputedVar value.
- If the value is already cached on the instance, return the cached value.
- Args:
- instance: the instance of the class accessing this computed var.
- owner: the class that this descriptor is attached to.
- Returns:
- The value of the var for the given instance.
- """
- if instance is None:
- state_where_defined = owner
- while self._js_expr in state_where_defined.inherited_vars:
- state_where_defined = state_where_defined.get_parent_state()
- field_name = (
- format_state_name(state_where_defined.get_full_name())
- + "."
- + self._js_expr
- )
- return dispatch(
- field_name,
- var_data=VarData.from_state(state_where_defined, self._js_expr),
- result_var_type=self._var_type,
- existing_var=self,
- )
- if not self._cache:
- value = self.fget(instance)
- else:
- # handle caching
- if not hasattr(instance, self._cache_attr) or self.needs_update(instance):
- # Set cache attr on state instance.
- setattr(instance, self._cache_attr, self.fget(instance))
- # Ensure the computed var gets serialized to redis.
- instance._was_touched = True
- # Set the last updated timestamp on the state instance.
- setattr(instance, self._last_updated_attr, datetime.datetime.now())
- value = getattr(instance, self._cache_attr)
- self._check_deprecated_return_type(instance, value)
- return value
- def _check_deprecated_return_type(self, instance: BaseState, value: Any) -> None:
- if not _isinstance(value, self._var_type):
- console.error(
- f"Computed var '{type(instance).__name__}.{self._js_expr}' must return"
- f" type '{self._var_type}', got '{type(value)}'."
- )
- def _deps(
- self,
- objclass: Type[BaseState],
- obj: FunctionType | CodeType | None = None,
- ) -> dict[str, set[str]]:
- """Determine var dependencies of this ComputedVar.
- Save references to attributes accessed on "self" or other fetched states.
- Recursively called when the function makes a method call on "self" or
- define comprehensions or nested functions that may reference "self".
- Args:
- objclass: the class obj this ComputedVar is attached to.
- obj: the object to disassemble (defaults to the fget function).
- Returns:
- A dictionary mapping state names to the set of variable names
- accessed by the given obj.
- """
- from .dep_tracking import DependencyTracker
- d = {}
- if self._static_deps:
- d.update(self._static_deps)
- # None is a placeholder for the current state class.
- if None in d:
- d[objclass.get_full_name()] = d.pop(None)
- if not self._auto_deps:
- return d
- if obj is None:
- fget = self._fget
- if fget is not None:
- obj = cast(FunctionType, fget)
- else:
- return d
- try:
- return DependencyTracker(
- func=obj, state_cls=objclass, dependencies=d
- ).dependencies
- except Exception as e:
- console.warn(
- "Failed to automatically determine dependencies for computed var "
- f"{objclass.__name__}.{self._js_expr}: {e}. "
- "Provide static_deps and set auto_deps=False to suppress this warning."
- )
- return d
- def mark_dirty(self, instance: BaseState) -> None:
- """Mark this ComputedVar as dirty.
- Args:
- instance: the state instance that needs to recompute the value.
- """
- with contextlib.suppress(AttributeError):
- delattr(instance, self._cache_attr)
- def add_dependency(self, objclass: Type[BaseState], dep: Var):
- """Explicitly add a dependency to the ComputedVar.
- After adding the dependency, when the `dep` changes, this computed var
- will be marked dirty.
- Args:
- objclass: The class obj this ComputedVar is attached to.
- dep: The dependency to add.
- Raises:
- VarDependencyError: If the dependency is not a Var instance with a
- state and field name
- """
- if all_var_data := dep._get_all_var_data():
- state_name = all_var_data.state
- if state_name:
- var_name = all_var_data.field_name
- if var_name:
- self._static_deps.setdefault(state_name, set()).add(var_name)
- objclass.get_root_state().get_class_substate(
- state_name
- )._var_dependencies.setdefault(var_name, set()).add(
- (objclass.get_full_name(), self._js_expr)
- )
- return
- raise VarDependencyError(
- "ComputedVar dependencies must be Var instances with a state and "
- f"field name, got {dep!r}."
- )
- def _determine_var_type(self) -> GenericType:
- """Get the type of the var.
- Returns:
- The type of the var.
- """
- hints = get_type_hints(self._fget)
- if "return" in hints:
- return hints["return"]
- return Any # pyright: ignore [reportReturnType]
- @property
- def __class__(self) -> Type:
- """Get the class of the var.
- Returns:
- The class of the var.
- """
- return FakeComputedVarBaseClass
- @property
- def fget(self) -> Callable[[BaseState], RETURN_TYPE]:
- """Get the getter function.
- Returns:
- The getter function.
- """
- return self._fget
- class DynamicRouteVar(ComputedVar[Union[str, List[str]]]):
- """A ComputedVar that represents a dynamic route."""
- pass
- async def _default_async_computed_var(_self: BaseState) -> Any:
- return None
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- init=False,
- slots=True,
- )
- class AsyncComputedVar(ComputedVar[RETURN_TYPE]):
- """A computed var that wraps a coroutinefunction."""
- _fget: Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]] = (
- dataclasses.field(default=_default_async_computed_var)
- )
- @overload
- def __get__(
- self: AsyncComputedVar[bool],
- instance: None,
- owner: Type,
- ) -> BooleanVar: ...
- @overload
- def __get__(
- self: AsyncComputedVar[int] | ComputedVar[float],
- instance: None,
- owner: Type,
- ) -> NumberVar: ...
- @overload
- def __get__(
- self: AsyncComputedVar[str],
- instance: None,
- owner: Type,
- ) -> StringVar: ...
- @overload
- def __get__(
- self: AsyncComputedVar[MAPPING_TYPE],
- instance: None,
- owner: Type,
- ) -> ObjectVar[MAPPING_TYPE]: ...
- @overload
- def __get__(
- self: AsyncComputedVar[SEQUENCE_TYPE],
- instance: None,
- owner: Type,
- ) -> ArrayVar[SEQUENCE_TYPE]: ...
- @overload
- def __get__(
- self: AsyncComputedVar[BASE_TYPE],
- instance: None,
- owner: Type,
- ) -> ObjectVar[BASE_TYPE]: ...
- @overload
- def __get__(
- self: AsyncComputedVar[SQLA_TYPE],
- instance: None,
- owner: Type,
- ) -> ObjectVar[SQLA_TYPE]: ...
- if TYPE_CHECKING:
- @overload
- def __get__(
- self: AsyncComputedVar[DATACLASS_TYPE], instance: None, owner: Any
- ) -> ObjectVar[DATACLASS_TYPE]: ...
- @overload
- def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ...
- @overload
- def __get__(
- self, instance: BaseState, owner: Type
- ) -> Coroutine[None, None, RETURN_TYPE]: ...
- def __get__(
- self, instance: BaseState | None, owner
- ) -> Var | Coroutine[None, None, RETURN_TYPE]:
- """Get the ComputedVar value.
- If the value is already cached on the instance, return the cached value.
- Args:
- instance: the instance of the class accessing this computed var.
- owner: the class that this descriptor is attached to.
- Returns:
- The value of the var for the given instance.
- """
- if instance is None:
- return super(AsyncComputedVar, self).__get__(instance, owner)
- if not self._cache:
- async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
- value = await self.fget(instance)
- self._check_deprecated_return_type(instance, value)
- return value
- return _awaitable_result()
- else:
- # handle caching
- async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE:
- if not hasattr(instance, self._cache_attr) or self.needs_update(
- instance
- ):
- # Set cache attr on state instance.
- setattr(instance, self._cache_attr, await self.fget(instance))
- # Ensure the computed var gets serialized to redis.
- instance._was_touched = True
- # Set the last updated timestamp on the state instance.
- setattr(instance, self._last_updated_attr, datetime.datetime.now())
- value = getattr(instance, self._cache_attr)
- self._check_deprecated_return_type(instance, value)
- return value
- return _awaitable_result()
- @property
- def fget(self) -> Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]]:
- """Get the getter function.
- Returns:
- The getter function.
- """
- return self._fget
- if TYPE_CHECKING:
- BASE_STATE = TypeVar("BASE_STATE", bound=BaseState)
- @overload
- def computed_var(
- fget: None = None,
- initial_value: Any | types.Unset = types.Unset(),
- cache: bool = True,
- deps: Optional[List[Union[str, Var]]] = None,
- auto_deps: bool = True,
- interval: Optional[Union[datetime.timedelta, int]] = None,
- backend: bool | None = None,
- **kwargs,
- ) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ... # pyright: ignore [reportInvalidTypeVarUse]
- @overload
- def computed_var(
- fget: Callable[[BASE_STATE], RETURN_TYPE],
- initial_value: RETURN_TYPE | types.Unset = types.Unset(),
- cache: bool = True,
- deps: Optional[List[Union[str, Var]]] = None,
- auto_deps: bool = True,
- interval: Optional[Union[datetime.timedelta, int]] = None,
- backend: bool | None = None,
- **kwargs,
- ) -> ComputedVar[RETURN_TYPE]: ...
- def computed_var(
- fget: Callable[[BASE_STATE], Any] | None = None,
- initial_value: Any | types.Unset = types.Unset(),
- cache: bool = True,
- deps: Optional[List[Union[str, Var]]] = None,
- auto_deps: bool = True,
- interval: Optional[Union[datetime.timedelta, int]] = None,
- backend: bool | None = None,
- **kwargs,
- ) -> ComputedVar | Callable[[Callable[[BASE_STATE], Any]], ComputedVar]:
- """A ComputedVar decorator with or without kwargs.
- Args:
- fget: The getter function.
- initial_value: The initial value of the computed var.
- cache: Whether to cache the computed value.
- deps: Explicit var dependencies to track.
- auto_deps: Whether var dependencies should be auto-determined.
- interval: Interval at which the computed var should be updated.
- backend: Whether the computed var is a backend var.
- **kwargs: additional attributes to set on the instance
- Returns:
- A ComputedVar instance.
- Raises:
- ValueError: If caching is disabled and an update interval is set.
- VarDependencyError: If user supplies dependencies without caching.
- ComputedVarSignatureError: If the getter function has more than one argument.
- """
- if cache is False and interval is not None:
- raise ValueError("Cannot set update interval without caching.")
- if cache is False and (deps is not None or auto_deps is False):
- raise VarDependencyError("Cannot track dependencies without caching.")
- if fget is not None:
- sign = inspect.signature(fget)
- if len(sign.parameters) != 1:
- raise ComputedVarSignatureError(fget.__name__, signature=str(sign))
- if inspect.iscoroutinefunction(fget):
- computed_var_cls = AsyncComputedVar
- else:
- computed_var_cls = ComputedVar
- return computed_var_cls(
- fget,
- initial_value=initial_value,
- cache=cache,
- deps=deps,
- auto_deps=auto_deps,
- interval=interval,
- backend=backend,
- **kwargs,
- )
- def wrapper(fget: Callable[[BASE_STATE], Any]) -> ComputedVar:
- if inspect.iscoroutinefunction(fget):
- computed_var_cls = AsyncComputedVar
- else:
- computed_var_cls = ComputedVar
- return computed_var_cls(
- fget,
- initial_value=initial_value,
- cache=cache,
- deps=deps,
- auto_deps=auto_deps,
- interval=interval,
- backend=backend,
- **kwargs,
- )
- return wrapper
- RETURN = TypeVar("RETURN")
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- slots=True,
- )
- class CustomVarOperationReturn(Var[RETURN]):
- """Base class for custom var operations."""
- _type_computer: TypeComputer | None = dataclasses.field(default=None)
- _raw_js_function: str | None = dataclasses.field(default=None)
- @classmethod
- def create(
- cls,
- js_expression: str,
- _var_type: Type[RETURN] | None = None,
- _type_computer: TypeComputer | None = None,
- _var_data: VarData | None = None,
- _raw_js_function: str | None = None,
- ) -> CustomVarOperationReturn[RETURN]:
- """Create a CustomVarOperation.
- Args:
- js_expression: The JavaScript expression to evaluate.
- _var_type: The type of the var.
- _type_computer: A function to compute the type of the var given the arguments.
- _var_data: Additional hooks and imports associated with the Var.
- _raw_js_function: If provided, it will be used when the operation is being called with all of its arguments at once.
- Returns:
- The CustomVarOperation.
- """
- return CustomVarOperationReturn(
- _js_expr=js_expression,
- _var_type=_var_type or Any,
- _type_computer=_type_computer,
- _var_data=_var_data,
- _raw_js_function=_raw_js_function,
- )
- def var_operation_return(
- js_expression: str,
- var_type: Type[RETURN] | None = None,
- type_computer: Optional[TypeComputer] = None,
- var_data: VarData | None = None,
- _raw_js_function: str | None = None,
- ) -> CustomVarOperationReturn[RETURN]:
- """Shortcut for creating a CustomVarOperationReturn.
- Args:
- js_expression: The JavaScript expression to evaluate.
- var_type: The type of the var.
- type_computer: A function to compute the type of the var given the arguments.
- var_data: Additional hooks and imports associated with the Var.
- _raw_js_function: If provided, it will be used when the operation is being called with all of its arguments at once.
- Returns:
- The CustomVarOperationReturn.
- """
- return CustomVarOperationReturn.create(
- js_expression=js_expression,
- _var_type=var_type,
- _type_computer=type_computer,
- _var_data=var_data,
- _raw_js_function=_raw_js_function,
- )
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- slots=True,
- )
- class CustomVarOperation(CachedVarOperation, Var[T]):
- """Base class for custom var operations."""
- _name: str = dataclasses.field(default="")
- _args: Tuple[Tuple[str, Var], ...] = dataclasses.field(default_factory=tuple)
- _return: CustomVarOperationReturn[T] = dataclasses.field(
- default_factory=lambda: CustomVarOperationReturn.create("")
- )
- @cached_property_no_lock
- def _cached_var_name(self) -> str:
- """Get the cached var name.
- Returns:
- The cached var name.
- """
- return str(self._return)
- @cached_property_no_lock
- def _cached_get_all_var_data(self) -> VarData | None:
- """Get the cached VarData.
- Returns:
- The cached VarData.
- """
- return VarData.merge(
- *(arg[1]._get_all_var_data() for arg in self._args),
- self._return._get_all_var_data(),
- self._var_data,
- )
- @classmethod
- def create(
- cls,
- name: str,
- args: Tuple[Tuple[str, Var], ...],
- return_var: CustomVarOperationReturn[T],
- _var_data: VarData | None = None,
- ) -> CustomVarOperation[T]:
- """Create a CustomVarOperation.
- Args:
- name: The name of the operation.
- args: The arguments to the operation.
- return_var: The return var.
- _var_data: Additional hooks and imports associated with the Var.
- Returns:
- The CustomVarOperation.
- """
- return CustomVarOperation(
- _js_expr="",
- _var_type=return_var._var_type,
- _var_data=_var_data,
- _name=name,
- _args=args,
- _return=return_var,
- )
- class NoneVar(Var[None], python_types=type(None)):
- """A var representing None."""
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- slots=True,
- )
- class LiteralNoneVar(LiteralVar, NoneVar):
- """A var representing None."""
- _var_value: None = None
- def json(self) -> str:
- """Serialize the var to a JSON string.
- Returns:
- The JSON string.
- """
- return "null"
- @classmethod
- def create(
- cls,
- value: None = None,
- _var_data: VarData | None = None,
- ) -> LiteralNoneVar:
- """Create a var from a value.
- Args:
- value: The value of the var. Must be None. Existed for compatibility with LiteralVar.
- _var_data: Additional hooks and imports associated with the Var.
- Returns:
- The var.
- """
- return LiteralNoneVar(
- _js_expr="null",
- _var_type=None,
- _var_data=_var_data,
- )
- def get_to_operation(var_subclass: Type[Var]) -> Type[ToOperation]:
- """Get the ToOperation class for a given Var subclass.
- Args:
- var_subclass: The Var subclass.
- Returns:
- The ToOperation class.
- Raises:
- ValueError: If the ToOperation class cannot be found.
- """
- possible_classes = [
- saved_var_subclass.to_var_subclass
- for saved_var_subclass in _var_subclasses
- if saved_var_subclass.var_subclass is var_subclass
- ]
- if not possible_classes:
- raise ValueError(f"Could not find ToOperation for {var_subclass}.")
- return possible_classes[0]
- @dataclasses.dataclass(
- eq=False,
- frozen=True,
- slots=True,
- )
- class StateOperation(CachedVarOperation, Var):
- """A var operation that accesses a field on an object."""
- _state_name: str = dataclasses.field(default="")
- _field: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
- @cached_property_no_lock
- def _cached_var_name(self) -> str:
- """Get the cached var name.
- Returns:
- The cached var name.
- """
- return f"{self._state_name!s}.{self._field!s}"
- def __getattr__(self, name: str) -> Any:
- """Get an attribute of the var.
- Args:
- name: The name of the attribute.
- Returns:
- The attribute.
- """
- if name == "_js_expr":
- return self._cached_var_name
- return getattr(self._field, name)
- @classmethod
- def create(
- cls,
- state_name: str,
- field: Var,
- _var_data: VarData | None = None,
- ) -> StateOperation:
- """Create a DotOperation.
- Args:
- state_name: The name of the state.
- field: The field of the state.
- _var_data: Additional hooks and imports associated with the Var.
- Returns:
- The DotOperation.
- """
- return StateOperation(
- _js_expr="",
- _var_type=field._var_type,
- _var_data=_var_data,
- _state_name=state_name,
- _field=field,
- )
- def get_uuid_string_var() -> Var:
- """Return a Var that generates a single memoized UUID via .web/utils/state.js.
- useMemo with an empty dependency array ensures that the generated UUID is
- consistent across re-renders of the component.
- Returns:
- A Var that generates a UUID at runtime.
- """
- from reflex.utils.imports import ImportVar
- from reflex.vars import Var
- unique_uuid_var = get_unique_variable_name()
- unique_uuid_var_data = VarData(
- imports={
- f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # pyright: ignore [reportArgumentType]
- "react": "useMemo",
- },
- hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None},
- )
- return Var(
- _js_expr=unique_uuid_var,
- _var_type=str,
- _var_data=unique_uuid_var_data,
- )
- # Set of unique variable names.
- USED_VARIABLES = set()
- def get_unique_variable_name() -> str:
- """Get a unique variable name.
- Returns:
- The unique variable name.
- """
- name = "".join([random.choice(string.ascii_lowercase) for _ in range(8)])
- if name not in USED_VARIABLES:
- USED_VARIABLES.add(name)
- return name
- return get_unique_variable_name()
- # Compile regex for finding reflex var tags.
- _decode_var_pattern_re = (
- rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
- )
- _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
- # Defined global immutable vars.
- _global_vars: Dict[int, Var] = {}
- def _extract_var_data(value: Iterable) -> list[VarData | None]:
- """Extract the var imports and hooks from an iterable containing a Var.
- Args:
- value: The iterable to extract the VarData from
- Returns:
- The extracted VarDatas.
- """
- from reflex.style import Style
- from reflex.vars import Var
- var_datas = []
- with contextlib.suppress(TypeError):
- for sub in value:
- if isinstance(sub, Var):
- var_datas.append(sub._var_data)
- elif not isinstance(sub, str):
- # Recurse into dict values.
- if (
- (values_fn := getattr(sub, "values", None)) is not None
- and callable(values_fn)
- and isinstance((values := values_fn()), Iterable)
- ):
- var_datas.extend(_extract_var_data(values))
- # Recurse into iterable values (or dict keys).
- var_datas.extend(_extract_var_data(sub))
- # Style objects should already have _var_data.
- if isinstance(value, Style):
- var_datas.append(value._var_data)
- else:
- # Recurse when value is a dict itself.
- values_fn = getattr(value, "values", None)
- if callable(values_fn) and isinstance((values := values_fn()), Iterable):
- var_datas.extend(_extract_var_data(values))
- return var_datas
- dispatchers: Dict[GenericType, Callable[[Var], Var]] = {}
- def transform(fn: Callable[[Var], Var]) -> Callable[[Var], Var]:
- """Register a function to transform a Var.
- Args:
- fn: The function to register.
- Returns:
- The decorator.
- Raises:
- TypeError: If the return type of the function is not a Var.
- TypeError: If the Var return type does not have a generic type.
- ValueError: If a function for the generic type is already registered.
- """
- return_type = fn.__annotations__["return"]
- origin = get_origin(return_type)
- if origin is not Var:
- raise TypeError(
- f"Expected return type of {fn.__name__} to be a Var, got {origin}."
- )
- generic_args = get_args(return_type)
- if not generic_args:
- raise TypeError(
- f"Expected Var return type of {fn.__name__} to have a generic type."
- )
- generic_type = get_origin(generic_args[0]) or generic_args[0]
- if generic_type in dispatchers:
- raise ValueError(f"Function for {generic_type} already registered.")
- dispatchers[generic_type] = fn
- return fn
- def generic_type_to_actual_type_map(
- generic_type: GenericType, actual_type: GenericType
- ) -> Dict[TypeVar, GenericType]:
- """Map the generic type to the actual type.
- Args:
- generic_type: The generic type.
- actual_type: The actual type.
- Returns:
- The mapping of type variables to actual types.
- Raises:
- TypeError: If the generic type and actual type do not match.
- TypeError: If the number of generic arguments and actual arguments do not match.
- """
- generic_origin = get_origin(generic_type) or generic_type
- actual_origin = get_origin(actual_type) or actual_type
- if generic_origin is not actual_origin:
- if isinstance(generic_origin, TypeVar):
- return {generic_origin: actual_origin}
- raise TypeError(
- f"Type mismatch: expected {generic_origin}, got {actual_origin}."
- )
- generic_args = get_args(generic_type)
- actual_args = get_args(actual_type)
- if len(generic_args) != len(actual_args):
- raise TypeError(
- f"Number of generic arguments mismatch: expected {len(generic_args)}, got {len(actual_args)}."
- )
- # call recursively for nested generic types and merge the results
- return {
- k: v
- for generic_arg, actual_arg in zip(generic_args, actual_args, strict=True)
- for k, v in generic_type_to_actual_type_map(generic_arg, actual_arg).items()
- }
- def resolve_generic_type_with_mapping(
- generic_type: GenericType, type_mapping: Dict[TypeVar, GenericType]
- ):
- """Resolve a generic type with a type mapping.
- Args:
- generic_type: The generic type.
- type_mapping: The type mapping.
- Returns:
- The resolved generic type.
- """
- if isinstance(generic_type, TypeVar):
- return type_mapping.get(generic_type, generic_type)
- generic_origin = get_origin(generic_type) or generic_type
- generic_args = get_args(generic_type)
- if not generic_args:
- return generic_type
- mapping_for_older_python = {
- list: List,
- set: Set,
- dict: Dict,
- tuple: Tuple,
- frozenset: FrozenSet,
- }
- return mapping_for_older_python.get(generic_origin, generic_origin)[
- tuple(
- resolve_generic_type_with_mapping(arg, type_mapping) for arg in generic_args
- )
- ]
- def resolve_arg_type_from_return_type(
- arg_type: GenericType, return_type: GenericType, actual_return_type: GenericType
- ) -> GenericType:
- """Resolve the argument type from the return type.
- Args:
- arg_type: The argument type.
- return_type: The return type.
- actual_return_type: The requested return type.
- Returns:
- The argument type without the generics that are resolved.
- """
- return resolve_generic_type_with_mapping(
- arg_type, generic_type_to_actual_type_map(return_type, actual_return_type)
- )
- def dispatch(
- field_name: str,
- var_data: VarData,
- result_var_type: GenericType,
- existing_var: Var | None = None,
- ) -> Var:
- """Dispatch a Var to the appropriate transformation function.
- Args:
- field_name: The name of the field.
- var_data: The VarData associated with the Var.
- result_var_type: The type of the Var.
- existing_var: The existing Var to transform. Optional.
- Returns:
- The transformed Var.
- Raises:
- TypeError: If the return type of the function is not a Var.
- TypeError: If the Var return type does not have a generic type.
- TypeError: If the first argument of the function is not a Var.
- TypeError: If the first argument of the function does not have a generic type
- """
- result_origin_var_type = get_origin(result_var_type) or result_var_type
- if result_origin_var_type in dispatchers:
- fn = dispatchers[result_origin_var_type]
- fn_first_arg_type = next(
- iter(inspect.signature(fn).parameters.values())
- ).annotation
- fn_return = inspect.signature(fn).return_annotation
- fn_return_origin = get_origin(fn_return) or fn_return
- if fn_return_origin is not Var:
- raise TypeError(
- f"Expected return type of {fn.__name__} to be a Var, got {fn_return}."
- )
- fn_return_generic_args = get_args(fn_return)
- if not fn_return_generic_args:
- raise TypeError(f"Expected generic type of {fn_return} to be a type.")
- arg_origin = get_origin(fn_first_arg_type) or fn_first_arg_type
- if arg_origin is not Var:
- raise TypeError(
- f"Expected first argument of {fn.__name__} to be a Var, got {fn_first_arg_type}."
- )
- arg_generic_args = get_args(fn_first_arg_type)
- if not arg_generic_args:
- raise TypeError(
- f"Expected generic type of {fn_first_arg_type} to be a type."
- )
- arg_type = arg_generic_args[0]
- fn_return_type = fn_return_generic_args[0]
- var = (
- Var(
- field_name,
- _var_data=var_data,
- _var_type=resolve_arg_type_from_return_type(
- arg_type, fn_return_type, result_var_type
- ),
- ).guess_type()
- if existing_var is None
- else existing_var._replace(
- _var_type=resolve_arg_type_from_return_type(
- arg_type, fn_return_type, result_var_type
- ),
- _var_data=var_data,
- _js_expr=field_name,
- ).guess_type()
- )
- return fn(var)
- if existing_var is not None:
- return existing_var._replace(
- _js_expr=field_name,
- _var_data=var_data,
- _var_type=result_var_type,
- ).guess_type()
- return Var(
- field_name,
- _var_data=var_data,
- _var_type=result_var_type,
- ).guess_type()
- V = TypeVar("V")
- BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
- SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
- if TYPE_CHECKING:
- from _typeshed import DataclassInstance
- DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None)
- FIELD_TYPE = TypeVar("FIELD_TYPE")
- MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
- class Field(Generic[FIELD_TYPE]):
- """Shadow class for Var to allow for type hinting in the IDE."""
- def __set__(self, instance: Any, value: FIELD_TYPE):
- """Set the Var.
- Args:
- instance: The instance of the class setting the Var.
- value: The value to set the Var to.
- """
- @overload
- def __get__(self: Field[bool], instance: None, owner: Any) -> BooleanVar: ...
- @overload
- def __get__(self: Field[int], instance: None, owner: Any) -> NumberVar[int]: ...
- @overload
- def __get__(self: Field[float], instance: None, owner: Any) -> NumberVar[float]: ...
- @overload
- def __get__(self: Field[str], instance: None, owner: Any) -> StringVar[str]: ...
- @overload
- def __get__(self: Field[None], instance: None, owner: Any) -> NoneVar: ...
- @overload
- def __get__(
- self: Field[Sequence[V]] | Field[Set[V]] | Field[List[V]],
- instance: None,
- owner: Any,
- ) -> ArrayVar[Sequence[V]]: ...
- @overload
- def __get__(
- self: Field[MAPPING_TYPE], instance: None, owner: Any
- ) -> ObjectVar[MAPPING_TYPE]: ...
- @overload
- def __get__(
- self: Field[BASE_TYPE], instance: None, owner: Any
- ) -> ObjectVar[BASE_TYPE]: ...
- @overload
- def __get__(
- self: Field[SQLA_TYPE], instance: None, owner: Any
- ) -> ObjectVar[SQLA_TYPE]: ...
- if TYPE_CHECKING:
- @overload
- def __get__(
- self: Field[DATACLASS_TYPE], instance: None, owner: Any
- ) -> ObjectVar[DATACLASS_TYPE]: ...
- @overload
- def __get__(self, instance: None, owner: Any) -> Var[FIELD_TYPE]: ...
- @overload
- def __get__(self, instance: Any, owner: Any) -> FIELD_TYPE: ...
- def __get__(self, instance: Any, owner: Any): # pyright: ignore [reportInconsistentOverload]
- """Get the Var.
- Args:
- instance: The instance of the class accessing the Var.
- owner: The class that the Var is attached to.
- """
- def field(value: FIELD_TYPE) -> Field[FIELD_TYPE]:
- """Create a Field with a value.
- Args:
- value: The value of the Field.
- Returns:
- The Field.
- """
- return value # pyright: ignore [reportReturnType]
- def and_operation(a: Var | Any, b: Var | Any) -> Var:
- """Perform a logical AND operation on two variables.
- Args:
- a: The first variable.
- b: The second variable.
- Returns:
- The result of the logical AND operation.
- """
- return _and_operation(a, b)
- def or_operation(a: Var | Any, b: Var | Any) -> Var:
- """Perform a logical OR operation on two variables.
- Args:
- a: The first variable.
- b: The second variable.
- Returns:
- The result of the logical OR operation.
- """
- return _or_operation(a, b)
- def passthrough_unary_type_computer(no_args: GenericType) -> TypeComputer:
- """Create a type computer for unary operations.
- Args:
- no_args: The type to return when no arguments are provided.
- Returns:
- The type computer.
- """
- def type_computer(*args: Var):
- if not args:
- return (no_args, type_computer)
- return (ReflexCallable[[], args[0]._var_type], None)
- return type_computer
- def unary_type_computer(
- no_args: GenericType, computer: Callable[[Var], GenericType]
- ) -> TypeComputer:
- """Create a type computer for unary operations.
- Args:
- no_args: The type to return when no arguments are provided.
- computer: The function to compute the type.
- Returns:
- The type computer.
- """
- def type_computer(*args: Var):
- if not args:
- return (no_args, type_computer)
- return (ReflexCallable[[], computer(args[0])], None)
- return type_computer
- def nary_type_computer(
- *types: GenericType, computer: Callable[..., GenericType]
- ) -> TypeComputer:
- """Create a type computer for n-ary operations.
- Args:
- types: The types to return when no arguments are provided.
- computer: The function to compute the type.
- Returns:
- The type computer.
- """
- def type_computer(*args: Var):
- if len(args) != len(types):
- return (
- types[len(args)],
- functools.partial(type_computer, *args),
- )
- return (
- ReflexCallable[[], computer(args)],
- None,
- )
- return type_computer
- T_LOGICAL = TypeVar("T_LOGICAL")
- U_LOGICAL = TypeVar("U_LOGICAL")
- @var_operation
- def _and_operation(
- a: Var[T_LOGICAL], b: Var[U_LOGICAL]
- ) -> CustomVarOperationReturn[Union[T_LOGICAL, U_LOGICAL]]:
- """Perform a logical AND operation on two variables.
- Args:
- a: The first variable.
- b: The second variable.
- Returns:
- The result of the logical AND operation.
- """
- return var_operation_return(
- js_expression=f"({a} && {b})",
- type_computer=nary_type_computer(
- ReflexCallable[[Any, Any], Any],
- ReflexCallable[[Any], Any],
- computer=lambda args: unionize(
- args[0]._var_type,
- args[1]._var_type,
- ),
- ),
- )
- @var_operation
- def _or_operation(
- a: Var[T_LOGICAL], b: Var[U_LOGICAL]
- ) -> CustomVarOperationReturn[Union[T_LOGICAL, U_LOGICAL]]:
- """Perform a logical OR operation on two variables.
- Args:
- a: The first variable.
- b: The second variable.
- Returns:
- The result ocomputerf the logical OR operation.
- """
- return var_operation_return(
- js_expression=f"({a} || {b})",
- type_computer=nary_type_computer(
- ReflexCallable[[Any, Any], Any],
- ReflexCallable[[Any], Any],
- computer=lambda args: unionize(
- args[0]._var_type,
- args[1]._var_type,
- ),
- ),
- )
|