reflex 0.6.5a3__py3-none-any.whl → 0.6.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reflex might be problematic. Click here for more details.
- reflex/.templates/web/utils/state.js +39 -14
- reflex/__init__.py +3 -1
- reflex/__init__.pyi +3 -0
- reflex/app.py +24 -4
- reflex/assets.py +95 -0
- reflex/base.py +2 -2
- reflex/components/base/error_boundary.py +99 -36
- reflex/components/base/error_boundary.pyi +3 -4
- reflex/components/component.py +29 -7
- reflex/components/core/cond.py +8 -0
- reflex/components/core/upload.py +8 -5
- reflex/components/datadisplay/code.py +1 -1
- reflex/components/datadisplay/logo.py +26 -13
- reflex/components/el/__init__.pyi +2 -0
- reflex/components/el/elements/__init__.py +1 -0
- reflex/components/el/elements/__init__.pyi +3 -0
- reflex/components/el/elements/forms.py +1 -0
- reflex/components/el/elements/forms.pyi +1 -0
- reflex/components/moment/moment.py +4 -3
- reflex/components/moment/moment.pyi +12 -2
- reflex/components/plotly/plotly.py +1 -1
- reflex/components/radix/primitives/drawer.py +5 -23
- reflex/components/radix/themes/base.py +3 -0
- reflex/components/radix/themes/components/segmented_control.py +3 -1
- reflex/components/radix/themes/components/segmented_control.pyi +7 -2
- reflex/components/radix/themes/components/text_field.py +3 -0
- reflex/components/radix/themes/components/text_field.pyi +4 -0
- reflex/components/recharts/recharts.py +2 -14
- reflex/components/recharts/recharts.pyi +0 -1
- reflex/components/sonner/toast.py +23 -12
- reflex/components/sonner/toast.pyi +6 -6
- reflex/config.py +60 -9
- reflex/constants/base.py +12 -0
- reflex/constants/installer.py +3 -3
- reflex/constants/style.py +1 -1
- reflex/event.py +22 -5
- reflex/experimental/assets.py +14 -36
- reflex/reflex.py +16 -35
- reflex/state.py +90 -25
- reflex/utils/exceptions.py +4 -0
- reflex/utils/prerequisites.py +174 -40
- reflex/utils/redir.py +13 -4
- reflex/utils/serializers.py +52 -1
- reflex/utils/telemetry.py +2 -1
- reflex/utils/types.py +52 -1
- reflex/vars/base.py +18 -4
- reflex/vars/function.py +283 -37
- {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/METADATA +3 -2
- {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/RECORD +52 -51
- {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/LICENSE +0 -0
- {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/WHEEL +0 -0
- {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/entry_points.txt +0 -0
reflex/utils/prerequisites.py
CHANGED
|
@@ -34,7 +34,7 @@ from redis.asyncio import Redis
|
|
|
34
34
|
from reflex import constants, model
|
|
35
35
|
from reflex.compiler import templates
|
|
36
36
|
from reflex.config import Config, environment, get_config
|
|
37
|
-
from reflex.utils import console, net, path_ops, processes
|
|
37
|
+
from reflex.utils import console, net, path_ops, processes, redir
|
|
38
38
|
from reflex.utils.exceptions import (
|
|
39
39
|
GeneratedCodeHasNoFunctionDefs,
|
|
40
40
|
raise_system_package_missing_error,
|
|
@@ -93,6 +93,8 @@ def check_latest_package_version(package_name: str):
|
|
|
93
93
|
Args:
|
|
94
94
|
package_name: The name of the package.
|
|
95
95
|
"""
|
|
96
|
+
if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
|
|
97
|
+
return
|
|
96
98
|
try:
|
|
97
99
|
# Get the latest version from PyPI
|
|
98
100
|
current_version = importlib.metadata.version(package_name)
|
|
@@ -1209,7 +1211,7 @@ def check_schema_up_to_date():
|
|
|
1209
1211
|
)
|
|
1210
1212
|
|
|
1211
1213
|
|
|
1212
|
-
def
|
|
1214
|
+
def prompt_for_template_options(templates: list[Template]) -> str:
|
|
1213
1215
|
"""Prompt the user to specify a template.
|
|
1214
1216
|
|
|
1215
1217
|
Args:
|
|
@@ -1221,9 +1223,14 @@ def prompt_for_template(templates: list[Template]) -> str:
|
|
|
1221
1223
|
# Show the user the URLs of each template to preview.
|
|
1222
1224
|
console.print("\nGet started with a template:")
|
|
1223
1225
|
|
|
1226
|
+
def format_demo_url_str(url: str) -> str:
|
|
1227
|
+
return f" ({url})" if url else ""
|
|
1228
|
+
|
|
1224
1229
|
# Prompt the user to select a template.
|
|
1225
1230
|
id_to_name = {
|
|
1226
|
-
str(
|
|
1231
|
+
str(
|
|
1232
|
+
idx
|
|
1233
|
+
): f"{template.name.replace('_', ' ').replace('-', ' ')}{format_demo_url_str(template.demo_url)} - {template.description}"
|
|
1227
1234
|
for idx, template in enumerate(templates)
|
|
1228
1235
|
}
|
|
1229
1236
|
for id in range(len(id_to_name)):
|
|
@@ -1378,15 +1385,122 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
|
|
|
1378
1385
|
shutil.rmtree(unzip_dir)
|
|
1379
1386
|
|
|
1380
1387
|
|
|
1381
|
-
def
|
|
1388
|
+
def initialize_default_app(app_name: str):
|
|
1389
|
+
"""Initialize the default app.
|
|
1390
|
+
|
|
1391
|
+
Args:
|
|
1392
|
+
app_name: The name of the app.
|
|
1393
|
+
"""
|
|
1394
|
+
create_config(app_name)
|
|
1395
|
+
initialize_app_directory(app_name)
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
def validate_and_create_app_using_remote_template(app_name, template, templates):
|
|
1399
|
+
"""Validate and create an app using a remote template.
|
|
1400
|
+
|
|
1401
|
+
Args:
|
|
1402
|
+
app_name: The name of the app.
|
|
1403
|
+
template: The name of the template.
|
|
1404
|
+
templates: The available templates.
|
|
1405
|
+
|
|
1406
|
+
Raises:
|
|
1407
|
+
Exit: If the template is not found.
|
|
1408
|
+
"""
|
|
1409
|
+
# If user selects a template, it needs to exist.
|
|
1410
|
+
if template in templates:
|
|
1411
|
+
template_url = templates[template].code_url
|
|
1412
|
+
else:
|
|
1413
|
+
# Check if the template is a github repo.
|
|
1414
|
+
if template.startswith("https://github.com"):
|
|
1415
|
+
template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
|
|
1416
|
+
else:
|
|
1417
|
+
console.error(f"Template `{template}` not found.")
|
|
1418
|
+
raise typer.Exit(1)
|
|
1419
|
+
|
|
1420
|
+
if template_url is None:
|
|
1421
|
+
return
|
|
1422
|
+
|
|
1423
|
+
create_config_init_app_from_remote_template(
|
|
1424
|
+
app_name=app_name, template_url=template_url
|
|
1425
|
+
)
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
def generate_template_using_ai(template: str | None = None) -> str:
|
|
1429
|
+
"""Generate a template using AI(Flexgen).
|
|
1430
|
+
|
|
1431
|
+
Args:
|
|
1432
|
+
template: The name of the template.
|
|
1433
|
+
|
|
1434
|
+
Returns:
|
|
1435
|
+
The generation hash.
|
|
1436
|
+
|
|
1437
|
+
Raises:
|
|
1438
|
+
Exit: If the template and ai flags are used.
|
|
1439
|
+
"""
|
|
1440
|
+
if template is None:
|
|
1441
|
+
# If AI is requested and no template specified, redirect the user to reflex.build.
|
|
1442
|
+
return redir.reflex_build_redirect()
|
|
1443
|
+
elif is_generation_hash(template):
|
|
1444
|
+
# Otherwise treat the template as a generation hash.
|
|
1445
|
+
return template
|
|
1446
|
+
else:
|
|
1447
|
+
console.error(
|
|
1448
|
+
"Cannot use `--template` option with `--ai` option. Please remove `--template` option."
|
|
1449
|
+
)
|
|
1450
|
+
raise typer.Exit(2)
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def fetch_remote_templates(
|
|
1454
|
+
template: Optional[str] = None,
|
|
1455
|
+
) -> tuple[str, dict[str, Template]]:
|
|
1456
|
+
"""Fetch the available remote templates.
|
|
1457
|
+
|
|
1458
|
+
Args:
|
|
1459
|
+
template: The name of the template.
|
|
1460
|
+
|
|
1461
|
+
Returns:
|
|
1462
|
+
The selected template and the available templates.
|
|
1463
|
+
|
|
1464
|
+
Raises:
|
|
1465
|
+
Exit: If the template is not valid or if the template is not specified.
|
|
1466
|
+
"""
|
|
1467
|
+
available_templates = {}
|
|
1468
|
+
|
|
1469
|
+
try:
|
|
1470
|
+
# Get the available templates
|
|
1471
|
+
available_templates = fetch_app_templates(constants.Reflex.VERSION)
|
|
1472
|
+
except Exception as e:
|
|
1473
|
+
console.warn("Failed to fetch templates. Falling back to default template.")
|
|
1474
|
+
console.debug(f"Error while fetching templates: {e}")
|
|
1475
|
+
template = constants.Templates.DEFAULT
|
|
1476
|
+
|
|
1477
|
+
if template == constants.Templates.DEFAULT:
|
|
1478
|
+
return template, available_templates
|
|
1479
|
+
|
|
1480
|
+
if template in available_templates:
|
|
1481
|
+
return template, available_templates
|
|
1482
|
+
|
|
1483
|
+
else:
|
|
1484
|
+
if template is not None:
|
|
1485
|
+
console.error(f"{template!r} is not a valid template name.")
|
|
1486
|
+
console.print(
|
|
1487
|
+
f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
|
|
1488
|
+
)
|
|
1489
|
+
raise typer.Exit(0)
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
def initialize_app(
|
|
1493
|
+
app_name: str, template: str | None = None, ai: bool = False
|
|
1494
|
+
) -> str | None:
|
|
1382
1495
|
"""Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
|
|
1383
1496
|
|
|
1384
1497
|
Args:
|
|
1385
1498
|
app_name: The name of the app.
|
|
1386
1499
|
template: The name of the template to use.
|
|
1500
|
+
ai: Whether to use AI to generate the template.
|
|
1387
1501
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1502
|
+
Returns:
|
|
1503
|
+
The name of the template.
|
|
1390
1504
|
"""
|
|
1391
1505
|
# Local imports to avoid circular imports.
|
|
1392
1506
|
from reflex.utils import telemetry
|
|
@@ -1396,51 +1510,71 @@ def initialize_app(app_name: str, template: str | None = None):
|
|
|
1396
1510
|
telemetry.send("reinit")
|
|
1397
1511
|
return
|
|
1398
1512
|
|
|
1513
|
+
generation_hash = None
|
|
1514
|
+
if ai:
|
|
1515
|
+
generation_hash = generate_template_using_ai(template)
|
|
1516
|
+
template = constants.Templates.DEFAULT
|
|
1517
|
+
|
|
1399
1518
|
templates: dict[str, Template] = {}
|
|
1400
1519
|
|
|
1401
1520
|
# Don't fetch app templates if the user directly asked for DEFAULT.
|
|
1402
|
-
if template is None
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
template =
|
|
1521
|
+
if template is not None and (template not in (constants.Templates.DEFAULT,)):
|
|
1522
|
+
template, templates = fetch_remote_templates(template)
|
|
1523
|
+
|
|
1524
|
+
if template is None:
|
|
1525
|
+
template = prompt_for_template_options(get_init_cli_prompt_options())
|
|
1526
|
+
if template == constants.Templates.AI:
|
|
1527
|
+
generation_hash = generate_template_using_ai()
|
|
1528
|
+
# change to the default to allow creation of default app
|
|
1529
|
+
template = constants.Templates.DEFAULT
|
|
1530
|
+
elif template == constants.Templates.CHOOSE_TEMPLATES:
|
|
1531
|
+
template, templates = fetch_remote_templates()
|
|
1413
1532
|
|
|
1414
1533
|
# If the blank template is selected, create a blank app.
|
|
1415
|
-
if template
|
|
1534
|
+
if template in (constants.Templates.DEFAULT,):
|
|
1416
1535
|
# Default app creation behavior: a blank app.
|
|
1417
|
-
|
|
1418
|
-
initialize_app_directory(app_name)
|
|
1536
|
+
initialize_default_app(app_name)
|
|
1419
1537
|
else:
|
|
1420
|
-
|
|
1421
|
-
|
|
1538
|
+
validate_and_create_app_using_remote_template(
|
|
1539
|
+
app_name=app_name, template=template, templates=templates
|
|
1540
|
+
)
|
|
1422
1541
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
template_url = (
|
|
1430
|
-
f"{template.strip('/').replace('.git', '')}/archive/main.zip"
|
|
1431
|
-
)
|
|
1432
|
-
else:
|
|
1433
|
-
console.error(f"Template `{template}` not found.")
|
|
1434
|
-
raise typer.Exit(1)
|
|
1542
|
+
# If a reflex.build generation hash is available, download the code and apply it to the main module.
|
|
1543
|
+
if generation_hash:
|
|
1544
|
+
initialize_main_module_index_from_generation(
|
|
1545
|
+
app_name, generation_hash=generation_hash
|
|
1546
|
+
)
|
|
1547
|
+
telemetry.send("init", template=template)
|
|
1435
1548
|
|
|
1436
|
-
|
|
1437
|
-
return
|
|
1549
|
+
return template
|
|
1438
1550
|
|
|
1439
|
-
create_config_init_app_from_remote_template(
|
|
1440
|
-
app_name=app_name, template_url=template_url
|
|
1441
|
-
)
|
|
1442
1551
|
|
|
1443
|
-
|
|
1552
|
+
def get_init_cli_prompt_options() -> list[Template]:
|
|
1553
|
+
"""Get the CLI options for initializing a Reflex app.
|
|
1554
|
+
|
|
1555
|
+
Returns:
|
|
1556
|
+
The CLI options.
|
|
1557
|
+
"""
|
|
1558
|
+
return [
|
|
1559
|
+
Template(
|
|
1560
|
+
name=constants.Templates.DEFAULT,
|
|
1561
|
+
description="A blank Reflex app.",
|
|
1562
|
+
demo_url=constants.Templates.DEFAULT_TEMPLATE_URL,
|
|
1563
|
+
code_url="",
|
|
1564
|
+
),
|
|
1565
|
+
Template(
|
|
1566
|
+
name=constants.Templates.AI,
|
|
1567
|
+
description="Generate a template using AI [Experimental]",
|
|
1568
|
+
demo_url="",
|
|
1569
|
+
code_url="",
|
|
1570
|
+
),
|
|
1571
|
+
Template(
|
|
1572
|
+
name=constants.Templates.CHOOSE_TEMPLATES,
|
|
1573
|
+
description="Choose an existing template.",
|
|
1574
|
+
demo_url="",
|
|
1575
|
+
code_url="",
|
|
1576
|
+
),
|
|
1577
|
+
]
|
|
1444
1578
|
|
|
1445
1579
|
|
|
1446
1580
|
def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
|
reflex/utils/redir.py
CHANGED
|
@@ -10,6 +10,18 @@ from .. import constants
|
|
|
10
10
|
from . import console
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def open_browser(target_url: str) -> None:
|
|
14
|
+
"""Open a browser window to target_url.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
target_url: The URL to open in the browser.
|
|
18
|
+
"""
|
|
19
|
+
if not webbrowser.open(target_url):
|
|
20
|
+
console.warn(
|
|
21
|
+
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
13
25
|
def open_browser_and_wait(
|
|
14
26
|
target_url: str, poll_url: str, interval: int = 2
|
|
15
27
|
) -> httpx.Response:
|
|
@@ -23,10 +35,7 @@ def open_browser_and_wait(
|
|
|
23
35
|
Returns:
|
|
24
36
|
The response from the poll_url.
|
|
25
37
|
"""
|
|
26
|
-
|
|
27
|
-
console.warn(
|
|
28
|
-
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
|
|
29
|
-
)
|
|
38
|
+
open_browser(target_url)
|
|
30
39
|
console.info("[b]Complete the workflow in the browser to continue.[/b]")
|
|
31
40
|
while True:
|
|
32
41
|
try:
|
reflex/utils/serializers.py
CHANGED
|
@@ -263,7 +263,58 @@ def serialize_base(value: Base) -> dict:
|
|
|
263
263
|
Returns:
|
|
264
264
|
The serialized Base.
|
|
265
265
|
"""
|
|
266
|
-
|
|
266
|
+
from reflex.vars.base import Var
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
k: v for k, v in value.dict().items() if isinstance(v, Var) or not callable(v)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
from pydantic.v1 import BaseModel as BaseModelV1
|
|
275
|
+
|
|
276
|
+
@serializer(to=dict)
|
|
277
|
+
def serialize_base_model_v1(model: BaseModelV1) -> dict:
|
|
278
|
+
"""Serialize a pydantic v1 BaseModel instance.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
model: The BaseModel to serialize.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
The serialized BaseModel.
|
|
285
|
+
"""
|
|
286
|
+
return model.dict()
|
|
287
|
+
|
|
288
|
+
from pydantic import BaseModel as BaseModelV2
|
|
289
|
+
|
|
290
|
+
if BaseModelV1 is not BaseModelV2:
|
|
291
|
+
|
|
292
|
+
@serializer(to=dict)
|
|
293
|
+
def serialize_base_model_v2(model: BaseModelV2) -> dict:
|
|
294
|
+
"""Serialize a pydantic v2 BaseModel instance.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
model: The BaseModel to serialize.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
The serialized BaseModel.
|
|
301
|
+
"""
|
|
302
|
+
return model.model_dump()
|
|
303
|
+
except ImportError:
|
|
304
|
+
# Older pydantic v1 import
|
|
305
|
+
from pydantic import BaseModel as BaseModelV1
|
|
306
|
+
|
|
307
|
+
@serializer(to=dict)
|
|
308
|
+
def serialize_base_model_v1(model: BaseModelV1) -> dict:
|
|
309
|
+
"""Serialize a pydantic v1 BaseModel instance.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
model: The BaseModel to serialize.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
The serialized BaseModel.
|
|
316
|
+
"""
|
|
317
|
+
return model.dict()
|
|
267
318
|
|
|
268
319
|
|
|
269
320
|
@serializer
|
reflex/utils/telemetry.py
CHANGED
|
@@ -51,7 +51,8 @@ def get_python_version() -> str:
|
|
|
51
51
|
Returns:
|
|
52
52
|
The Python version.
|
|
53
53
|
"""
|
|
54
|
-
|
|
54
|
+
# Remove the "+" from the version string in case user is using a pre-release version.
|
|
55
|
+
return platform.python_version().rstrip("+")
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
def get_reflex_version() -> str:
|
reflex/utils/types.py
CHANGED
|
@@ -14,9 +14,11 @@ from typing import (
|
|
|
14
14
|
Callable,
|
|
15
15
|
ClassVar,
|
|
16
16
|
Dict,
|
|
17
|
+
FrozenSet,
|
|
17
18
|
Iterable,
|
|
18
19
|
List,
|
|
19
20
|
Literal,
|
|
21
|
+
Mapping,
|
|
20
22
|
Optional,
|
|
21
23
|
Sequence,
|
|
22
24
|
Tuple,
|
|
@@ -29,6 +31,7 @@ from typing import (
|
|
|
29
31
|
from typing import get_origin as get_origin_og
|
|
30
32
|
|
|
31
33
|
import sqlalchemy
|
|
34
|
+
from typing_extensions import is_typeddict
|
|
32
35
|
|
|
33
36
|
import reflex
|
|
34
37
|
from reflex.components.core.breakpoints import Breakpoints
|
|
@@ -494,6 +497,14 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None)
|
|
|
494
497
|
if isinstance(instance, Breakpoints):
|
|
495
498
|
return _breakpoints_satisfies_typing(cls_check, instance)
|
|
496
499
|
|
|
500
|
+
if isinstance(cls_check_base, tuple):
|
|
501
|
+
cls_check_base = tuple(
|
|
502
|
+
cls_check_one if not is_typeddict(cls_check_one) else dict
|
|
503
|
+
for cls_check_one in cls_check_base
|
|
504
|
+
)
|
|
505
|
+
if is_typeddict(cls_check_base):
|
|
506
|
+
cls_check_base = dict
|
|
507
|
+
|
|
497
508
|
# Check if the types match.
|
|
498
509
|
try:
|
|
499
510
|
return cls_check_base == Any or issubclass(cls_base, cls_check_base)
|
|
@@ -503,6 +514,36 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None)
|
|
|
503
514
|
raise TypeError(f"Invalid type for issubclass: {cls_base}") from te
|
|
504
515
|
|
|
505
516
|
|
|
517
|
+
def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
|
|
518
|
+
"""Check if an object satisfies a typed dict.
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
obj: The object to check.
|
|
522
|
+
cls: The typed dict to check against.
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
Whether the object satisfies the typed dict.
|
|
526
|
+
"""
|
|
527
|
+
if not isinstance(obj, Mapping):
|
|
528
|
+
return False
|
|
529
|
+
|
|
530
|
+
key_names_to_values = get_type_hints(cls)
|
|
531
|
+
required_keys: FrozenSet[str] = getattr(cls, "__required_keys__", frozenset())
|
|
532
|
+
|
|
533
|
+
if not all(
|
|
534
|
+
isinstance(key, str)
|
|
535
|
+
and key in key_names_to_values
|
|
536
|
+
and _isinstance(value, key_names_to_values[key])
|
|
537
|
+
for key, value in obj.items()
|
|
538
|
+
):
|
|
539
|
+
return False
|
|
540
|
+
|
|
541
|
+
# TODO in 3.14: Implement https://peps.python.org/pep-0728/ if it's approved
|
|
542
|
+
|
|
543
|
+
# required keys are all present
|
|
544
|
+
return required_keys.issubset(required_keys)
|
|
545
|
+
|
|
546
|
+
|
|
506
547
|
def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
|
|
507
548
|
"""Check if an object is an instance of a class.
|
|
508
549
|
|
|
@@ -529,6 +570,16 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
|
|
|
529
570
|
origin = get_origin(cls)
|
|
530
571
|
|
|
531
572
|
if origin is None:
|
|
573
|
+
# cls is a typed dict
|
|
574
|
+
if is_typeddict(cls):
|
|
575
|
+
if nested:
|
|
576
|
+
return does_obj_satisfy_typed_dict(obj, cls)
|
|
577
|
+
return isinstance(obj, dict)
|
|
578
|
+
|
|
579
|
+
# cls is a float
|
|
580
|
+
if cls is float:
|
|
581
|
+
return isinstance(obj, (float, int))
|
|
582
|
+
|
|
532
583
|
# cls is a simple class
|
|
533
584
|
return isinstance(obj, cls)
|
|
534
585
|
|
|
@@ -553,7 +604,7 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
|
|
|
553
604
|
and len(obj) == len(args)
|
|
554
605
|
and all(_isinstance(item, arg) for item, arg in zip(obj, args))
|
|
555
606
|
)
|
|
556
|
-
if origin
|
|
607
|
+
if origin in (dict, Breakpoints):
|
|
557
608
|
return isinstance(obj, dict) and all(
|
|
558
609
|
_isinstance(key, args[0]) and _isinstance(value, args[1])
|
|
559
610
|
for key, value in obj.items()
|
reflex/vars/base.py
CHANGED
|
@@ -361,21 +361,29 @@ class Var(Generic[VAR_TYPE]):
|
|
|
361
361
|
return False
|
|
362
362
|
|
|
363
363
|
def __init_subclass__(
|
|
364
|
-
cls,
|
|
364
|
+
cls,
|
|
365
|
+
python_types: Tuple[GenericType, ...] | GenericType = types.Unset(),
|
|
366
|
+
default_type: GenericType = types.Unset(),
|
|
367
|
+
**kwargs,
|
|
365
368
|
):
|
|
366
369
|
"""Initialize the subclass.
|
|
367
370
|
|
|
368
371
|
Args:
|
|
369
372
|
python_types: The python types that the var represents.
|
|
373
|
+
default_type: The default type of the var. Defaults to the first python type.
|
|
370
374
|
**kwargs: Additional keyword arguments.
|
|
371
375
|
"""
|
|
372
376
|
super().__init_subclass__(**kwargs)
|
|
373
377
|
|
|
374
|
-
if python_types
|
|
378
|
+
if python_types or default_type:
|
|
375
379
|
python_types = (
|
|
376
|
-
python_types if isinstance(python_types, tuple) else (python_types,)
|
|
380
|
+
(python_types if isinstance(python_types, tuple) else (python_types,))
|
|
381
|
+
if python_types
|
|
382
|
+
else ()
|
|
377
383
|
)
|
|
378
384
|
|
|
385
|
+
default_type = default_type or (python_types[0] if python_types else Any)
|
|
386
|
+
|
|
379
387
|
@dataclasses.dataclass(
|
|
380
388
|
eq=False,
|
|
381
389
|
frozen=True,
|
|
@@ -388,7 +396,7 @@ class Var(Generic[VAR_TYPE]):
|
|
|
388
396
|
default=Var(_js_expr="null", _var_type=None),
|
|
389
397
|
)
|
|
390
398
|
|
|
391
|
-
_default_var_type: ClassVar[GenericType] =
|
|
399
|
+
_default_var_type: ClassVar[GenericType] = default_type
|
|
392
400
|
|
|
393
401
|
ToVarOperation.__name__ = f'To{cls.__name__.removesuffix("Var")}Operation'
|
|
394
402
|
|
|
@@ -588,6 +596,12 @@ class Var(Generic[VAR_TYPE]):
|
|
|
588
596
|
output: type[list] | type[tuple] | type[set],
|
|
589
597
|
) -> ArrayVar: ...
|
|
590
598
|
|
|
599
|
+
@overload
|
|
600
|
+
def to(
|
|
601
|
+
self,
|
|
602
|
+
output: type[dict],
|
|
603
|
+
) -> ObjectVar[dict]: ...
|
|
604
|
+
|
|
591
605
|
@overload
|
|
592
606
|
def to(
|
|
593
607
|
self, output: Type[ObjectVar], var_type: Type[VAR_INSIDE]
|