reflex 0.7.14a5__py3-none-any.whl → 0.8.0__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/jinja/app/rxconfig.py.jinja2 +4 -1
- reflex/.templates/jinja/web/package.json.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +21 -11
- reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
- reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -0
- reflex/.templates/jinja/web/styles/styles.css.jinja2 +1 -0
- reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
- reflex/.templates/web/app/entry.client.js +8 -0
- reflex/.templates/web/app/routes.js +10 -0
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
- reflex/.templates/web/postcss.config.js +1 -1
- reflex/.templates/web/react-router.config.js +6 -0
- reflex/.templates/web/styles/__reflex_style_reset.css +399 -0
- reflex/.templates/web/utils/client_side_routing.js +21 -19
- reflex/.templates/web/utils/react-theme.js +92 -0
- reflex/.templates/web/utils/state.js +251 -100
- reflex/.templates/web/vite-plugin-safari-cachebust.js +160 -0
- reflex/.templates/web/vite.config.js +39 -0
- reflex/__init__.py +1 -6
- reflex/__init__.pyi +327 -192
- reflex/app.py +103 -152
- reflex/base.py +1 -87
- reflex/compiler/compiler.py +70 -19
- reflex/compiler/templates.py +3 -3
- reflex/compiler/utils.py +91 -33
- reflex/components/__init__.py +0 -2
- reflex/components/__init__.pyi +34 -18
- reflex/components/base/__init__.py +1 -5
- reflex/components/base/__init__.pyi +30 -21
- reflex/components/base/app_wrap.pyi +7 -7
- reflex/components/base/body.pyi +7 -7
- reflex/components/base/document.py +18 -14
- reflex/components/base/document.pyi +88 -38
- reflex/components/base/error_boundary.pyi +7 -7
- reflex/components/base/fragment.pyi +7 -7
- reflex/components/base/link.pyi +12 -12
- reflex/components/base/meta.py +4 -15
- reflex/components/base/meta.pyi +31 -31
- reflex/components/base/script.py +60 -58
- reflex/components/base/script.pyi +248 -34
- reflex/components/base/strict_mode.pyi +7 -7
- reflex/components/component.py +146 -217
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +77 -37
- reflex/components/core/auto_scroll.pyi +7 -7
- reflex/components/core/banner.pyi +33 -33
- reflex/components/core/client_side_routing.py +7 -6
- reflex/components/core/client_side_routing.pyi +8 -59
- reflex/components/core/clipboard.pyi +7 -7
- reflex/components/core/debounce.py +1 -0
- reflex/components/core/debounce.pyi +7 -7
- reflex/components/core/foreach.py +5 -4
- reflex/components/core/helmet.py +14 -0
- reflex/components/{next/base.pyi → core/helmet.pyi} +12 -10
- reflex/components/core/html.pyi +7 -7
- reflex/components/core/match.py +3 -3
- reflex/components/core/sticky.pyi +21 -20
- reflex/components/core/upload.py +4 -2
- reflex/components/core/upload.pyi +26 -25
- reflex/components/datadisplay/__init__.pyi +13 -7
- reflex/components/datadisplay/code.py +14 -79
- reflex/components/datadisplay/code.pyi +11 -13
- reflex/components/datadisplay/dataeditor.pyi +38 -15
- reflex/components/datadisplay/shiki_code_block.py +5 -3
- reflex/components/datadisplay/shiki_code_block.pyi +16 -15
- reflex/components/dynamic.py +5 -5
- reflex/components/el/__init__.pyi +506 -246
- reflex/components/el/element.pyi +7 -7
- reflex/components/el/elements/__init__.pyi +504 -245
- reflex/components/el/elements/base.pyi +7 -7
- reflex/components/el/elements/forms.pyi +146 -101
- reflex/components/el/elements/inline.pyi +142 -142
- reflex/components/el/elements/media.pyi +131 -130
- reflex/components/el/elements/metadata.pyi +32 -32
- reflex/components/el/elements/other.pyi +37 -37
- reflex/components/el/elements/scripts.pyi +17 -17
- reflex/components/el/elements/sectioning.pyi +77 -77
- reflex/components/el/elements/tables.pyi +52 -52
- reflex/components/el/elements/typography.pyi +77 -77
- reflex/components/field.py +175 -0
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +14 -14
- reflex/components/lucide/icon.py +6 -2
- reflex/components/lucide/icon.pyi +19 -17
- reflex/components/markdown/markdown.py +5 -3
- reflex/components/markdown/markdown.pyi +7 -7
- reflex/components/moment/moment.py +1 -1
- reflex/components/moment/moment.pyi +7 -7
- reflex/components/plotly/plotly.py +12 -6
- reflex/components/plotly/plotly.pyi +50 -49
- reflex/components/props.py +376 -27
- reflex/components/radix/__init__.pyi +123 -65
- reflex/components/radix/primitives/__init__.pyi +6 -4
- reflex/components/radix/primitives/accordion.py +8 -1
- reflex/components/radix/primitives/accordion.pyi +37 -37
- reflex/components/radix/primitives/base.pyi +12 -12
- reflex/components/radix/primitives/drawer.pyi +56 -55
- reflex/components/radix/primitives/form.pyi +63 -53
- reflex/components/radix/primitives/progress.pyi +26 -25
- reflex/components/radix/primitives/slider.pyi +27 -27
- reflex/components/radix/themes/__init__.pyi +5 -6
- reflex/components/radix/themes/base.py +3 -3
- reflex/components/radix/themes/base.pyi +42 -42
- reflex/components/radix/themes/color_mode.py +5 -6
- reflex/components/radix/themes/color_mode.pyi +17 -17
- reflex/components/radix/themes/components/__init__.pyi +75 -38
- reflex/components/radix/themes/components/alert_dialog.pyi +37 -37
- reflex/components/radix/themes/components/aspect_ratio.pyi +7 -7
- reflex/components/radix/themes/components/avatar.pyi +7 -7
- reflex/components/radix/themes/components/badge.pyi +7 -7
- reflex/components/radix/themes/components/button.pyi +7 -7
- reflex/components/radix/themes/components/callout.pyi +26 -25
- reflex/components/radix/themes/components/card.pyi +7 -7
- reflex/components/radix/themes/components/checkbox.pyi +16 -15
- reflex/components/radix/themes/components/checkbox_cards.pyi +12 -12
- reflex/components/radix/themes/components/checkbox_group.pyi +12 -12
- reflex/components/radix/themes/components/context_menu.pyi +67 -67
- reflex/components/radix/themes/components/data_list.pyi +22 -22
- reflex/components/radix/themes/components/dialog.pyi +36 -35
- reflex/components/radix/themes/components/dropdown_menu.pyi +42 -42
- reflex/components/radix/themes/components/hover_card.pyi +21 -20
- reflex/components/radix/themes/components/icon_button.pyi +7 -7
- reflex/components/radix/themes/components/inset.pyi +7 -7
- reflex/components/radix/themes/components/popover.pyi +22 -22
- reflex/components/radix/themes/components/progress.pyi +7 -7
- reflex/components/radix/themes/components/radio.pyi +7 -7
- reflex/components/radix/themes/components/radio_cards.pyi +12 -12
- reflex/components/radix/themes/components/radio_group.pyi +21 -20
- reflex/components/radix/themes/components/scroll_area.pyi +7 -7
- reflex/components/radix/themes/components/segmented_control.pyi +12 -12
- reflex/components/radix/themes/components/select.pyi +46 -45
- reflex/components/radix/themes/components/separator.pyi +7 -7
- reflex/components/radix/themes/components/skeleton.pyi +7 -7
- reflex/components/radix/themes/components/slider.pyi +17 -9
- reflex/components/radix/themes/components/spinner.pyi +7 -7
- reflex/components/radix/themes/components/switch.pyi +7 -7
- reflex/components/radix/themes/components/table.pyi +37 -37
- reflex/components/radix/themes/components/tabs.pyi +26 -25
- reflex/components/radix/themes/components/text_area.pyi +15 -9
- reflex/components/radix/themes/components/text_field.pyi +32 -19
- reflex/components/radix/themes/components/tooltip.pyi +7 -7
- reflex/components/radix/themes/layout/__init__.pyi +27 -14
- reflex/components/radix/themes/layout/base.pyi +7 -7
- reflex/components/radix/themes/layout/box.pyi +7 -7
- reflex/components/radix/themes/layout/center.pyi +7 -7
- reflex/components/radix/themes/layout/container.pyi +7 -7
- reflex/components/radix/themes/layout/flex.pyi +7 -7
- reflex/components/radix/themes/layout/grid.pyi +7 -7
- reflex/components/radix/themes/layout/list.pyi +26 -25
- reflex/components/radix/themes/layout/section.pyi +7 -7
- reflex/components/radix/themes/layout/spacer.pyi +7 -7
- reflex/components/radix/themes/layout/stack.pyi +17 -17
- reflex/components/radix/themes/typography/__init__.pyi +7 -5
- reflex/components/radix/themes/typography/blockquote.pyi +7 -7
- reflex/components/radix/themes/typography/code.pyi +7 -7
- reflex/components/radix/themes/typography/heading.pyi +7 -7
- reflex/components/radix/themes/typography/link.py +46 -11
- reflex/components/radix/themes/typography/link.pyi +312 -9
- reflex/components/radix/themes/typography/text.pyi +36 -35
- reflex/components/react_player/audio.pyi +10 -8
- reflex/components/react_player/react_player.pyi +7 -7
- reflex/components/react_player/video.pyi +10 -8
- reflex/components/recharts/__init__.pyi +208 -100
- reflex/components/recharts/cartesian.py +10 -8
- reflex/components/recharts/cartesian.pyi +90 -94
- reflex/components/recharts/charts.py +4 -2
- reflex/components/recharts/charts.pyi +49 -49
- reflex/components/recharts/general.pyi +31 -31
- reflex/components/recharts/polar.py +8 -4
- reflex/components/recharts/polar.pyi +23 -23
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/recharts/recharts.pyi +12 -12
- reflex/components/sonner/toast.py +3 -3
- reflex/components/sonner/toast.pyi +9 -9
- reflex/config.py +10 -113
- reflex/constants/__init__.py +2 -2
- reflex/constants/base.py +28 -11
- reflex/constants/compiler.py +12 -3
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +26 -20
- reflex/constants/route.py +27 -8
- reflex/constants/state.py +2 -0
- reflex/custom_components/custom_components.py +0 -14
- reflex/environment.py +77 -5
- reflex/event.py +178 -81
- reflex/experimental/__init__.py +0 -30
- reflex/istate/__init__.py +69 -0
- reflex/istate/manager.py +1 -0
- reflex/istate/proxy.py +5 -3
- reflex/page.py +0 -27
- reflex/plugins/__init__.py +3 -2
- reflex/plugins/base.py +5 -1
- reflex/plugins/shared_tailwind.py +215 -0
- reflex/plugins/sitemap.py +206 -0
- reflex/plugins/tailwind_v3.py +15 -108
- reflex/plugins/tailwind_v4.py +18 -110
- reflex/reflex.py +1 -0
- reflex/route.py +157 -75
- reflex/state.py +171 -155
- reflex/testing.py +86 -16
- reflex/utils/build.py +38 -82
- reflex/utils/exec.py +83 -175
- reflex/utils/export.py +2 -2
- reflex/utils/format.py +1 -5
- reflex/utils/imports.py +5 -16
- reflex/utils/misc.py +67 -0
- reflex/utils/prerequisites.py +66 -68
- reflex/utils/processes.py +24 -47
- reflex/utils/pyi_generator.py +44 -49
- reflex/utils/serializers.py +14 -1
- reflex/utils/telemetry.py +0 -15
- reflex/utils/types.py +197 -62
- reflex/vars/__init__.py +2 -0
- reflex/vars/base.py +367 -134
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/METADATA +15 -8
- reflex-0.8.0.dist-info/RECORD +403 -0
- reflex/.templates/web/next.config.js +0 -7
- reflex/components/base/head.py +0 -20
- reflex/components/base/head.pyi +0 -116
- reflex/components/next/__init__.py +0 -10
- reflex/components/next/base.py +0 -7
- reflex/components/next/image.py +0 -117
- reflex/components/next/image.pyi +0 -94
- reflex/components/next/link.py +0 -20
- reflex/components/next/link.pyi +0 -67
- reflex/components/next/video.py +0 -38
- reflex/components/next/video.pyi +0 -68
- reflex/components/suneditor/__init__.py +0 -5
- reflex/components/suneditor/editor.py +0 -269
- reflex/components/suneditor/editor.pyi +0 -199
- reflex/experimental/layout.py +0 -254
- reflex-0.7.14a5.dist-info/RECORD +0 -407
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/WHEEL +0 -0
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/licenses/LICENSE +0 -0
reflex/utils/prerequisites.py
CHANGED
|
@@ -39,9 +39,8 @@ from reflex.compiler import templates
|
|
|
39
39
|
from reflex.config import Config, get_config
|
|
40
40
|
from reflex.environment import environment
|
|
41
41
|
from reflex.utils import console, net, path_ops, processes, redir
|
|
42
|
-
from reflex.utils.decorator import once
|
|
43
42
|
from reflex.utils.exceptions import SystemPackageMissingError
|
|
44
|
-
from reflex.utils.
|
|
43
|
+
from reflex.utils.misc import get_module_path
|
|
45
44
|
from reflex.utils.registry import get_npm_registry
|
|
46
45
|
|
|
47
46
|
if typing.TYPE_CHECKING:
|
|
@@ -74,7 +73,7 @@ class CpuInfo:
|
|
|
74
73
|
|
|
75
74
|
|
|
76
75
|
def get_web_dir() -> Path:
|
|
77
|
-
"""Get the working directory for the
|
|
76
|
+
"""Get the working directory for the frontend.
|
|
78
77
|
|
|
79
78
|
Can be overridden with REFLEX_WEB_WORKDIR.
|
|
80
79
|
|
|
@@ -117,7 +116,7 @@ def check_latest_package_version(package_name: str):
|
|
|
117
116
|
# Get the latest version from PyPI
|
|
118
117
|
current_version = importlib.metadata.version(package_name)
|
|
119
118
|
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
120
|
-
response = net.get(url)
|
|
119
|
+
response = net.get(url, timeout=2)
|
|
121
120
|
latest_version = response.json()["info"]["version"]
|
|
122
121
|
console.debug(f"Latest version of {package_name}: {latest_version}")
|
|
123
122
|
if get_or_set_last_reflex_version_check_datetime():
|
|
@@ -334,13 +333,14 @@ def npm_escape_hatch() -> bool:
|
|
|
334
333
|
|
|
335
334
|
|
|
336
335
|
def _check_app_name(config: Config):
|
|
337
|
-
"""Check if the app name is
|
|
336
|
+
"""Check if the app name is valid and matches the folder structure.
|
|
338
337
|
|
|
339
338
|
Args:
|
|
340
339
|
config: The config object.
|
|
341
340
|
|
|
342
341
|
Raises:
|
|
343
|
-
RuntimeError: If the app name is not set
|
|
342
|
+
RuntimeError: If the app name is not set, folder doesn't exist, or doesn't match config.
|
|
343
|
+
ModuleNotFoundError: If the app_name is not importable (i.e., not a valid Python package, folder structure being wrong).
|
|
344
344
|
"""
|
|
345
345
|
if not config.app_name:
|
|
346
346
|
msg = (
|
|
@@ -349,6 +349,18 @@ def _check_app_name(config: Config):
|
|
|
349
349
|
)
|
|
350
350
|
raise RuntimeError(msg)
|
|
351
351
|
|
|
352
|
+
from reflex.utils.misc import get_module_path, with_cwd_in_syspath
|
|
353
|
+
|
|
354
|
+
with with_cwd_in_syspath():
|
|
355
|
+
module_path = get_module_path(config.module)
|
|
356
|
+
if module_path is None:
|
|
357
|
+
msg = f"Module {config.module} not found. "
|
|
358
|
+
if config.app_module_import is not None:
|
|
359
|
+
msg += f"Ensure app_module_import='{config.app_module_import}' in rxconfig.py matches your folder structure."
|
|
360
|
+
else:
|
|
361
|
+
msg += f"Ensure app_name='{config.app_name}' in rxconfig.py matches your folder structure."
|
|
362
|
+
raise ModuleNotFoundError(msg)
|
|
363
|
+
|
|
352
364
|
|
|
353
365
|
def get_app(reload: bool = False) -> ModuleType:
|
|
354
366
|
"""Get the app module based on the default config.
|
|
@@ -439,7 +451,7 @@ def validate_app(
|
|
|
439
451
|
|
|
440
452
|
def get_compiled_app(
|
|
441
453
|
reload: bool = False,
|
|
442
|
-
|
|
454
|
+
prerender_routes: bool = False,
|
|
443
455
|
dry_run: bool = False,
|
|
444
456
|
check_if_schema_up_to_date: bool = False,
|
|
445
457
|
) -> ModuleType:
|
|
@@ -447,7 +459,7 @@ def get_compiled_app(
|
|
|
447
459
|
|
|
448
460
|
Args:
|
|
449
461
|
reload: Re-import the app module from disk
|
|
450
|
-
|
|
462
|
+
prerender_routes: Whether to prerender routes.
|
|
451
463
|
dry_run: If True, do not write the compiled app to disk.
|
|
452
464
|
check_if_schema_up_to_date: If True, check if the schema is up to date.
|
|
453
465
|
|
|
@@ -460,13 +472,13 @@ def get_compiled_app(
|
|
|
460
472
|
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
|
461
473
|
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
|
462
474
|
app._apply_decorated_pages()
|
|
463
|
-
app._compile(
|
|
475
|
+
app._compile(prerender_routes=prerender_routes, dry_run=dry_run)
|
|
464
476
|
return app_module
|
|
465
477
|
|
|
466
478
|
|
|
467
479
|
def compile_app(
|
|
468
480
|
reload: bool = False,
|
|
469
|
-
|
|
481
|
+
prerender_routes: bool = False,
|
|
470
482
|
dry_run: bool = False,
|
|
471
483
|
check_if_schema_up_to_date: bool = False,
|
|
472
484
|
) -> None:
|
|
@@ -474,13 +486,13 @@ def compile_app(
|
|
|
474
486
|
|
|
475
487
|
Args:
|
|
476
488
|
reload: Re-import the app module from disk
|
|
477
|
-
|
|
489
|
+
prerender_routes: Whether to prerender routes.
|
|
478
490
|
dry_run: If True, do not write the compiled app to disk.
|
|
479
491
|
check_if_schema_up_to_date: If True, check if the schema is up to date.
|
|
480
492
|
"""
|
|
481
493
|
get_compiled_app(
|
|
482
494
|
reload=reload,
|
|
483
|
-
|
|
495
|
+
prerender_routes=prerender_routes,
|
|
484
496
|
dry_run=dry_run,
|
|
485
497
|
check_if_schema_up_to_date=check_if_schema_up_to_date,
|
|
486
498
|
)
|
|
@@ -529,20 +541,26 @@ def _can_colorize() -> bool:
|
|
|
529
541
|
|
|
530
542
|
|
|
531
543
|
def compile_or_validate_app(
|
|
532
|
-
compile: bool = False,
|
|
544
|
+
compile: bool = False,
|
|
545
|
+
check_if_schema_up_to_date: bool = False,
|
|
546
|
+
prerender_routes: bool = False,
|
|
533
547
|
) -> bool:
|
|
534
548
|
"""Compile or validate the app module based on the default config.
|
|
535
549
|
|
|
536
550
|
Args:
|
|
537
551
|
compile: Whether to compile the app.
|
|
538
552
|
check_if_schema_up_to_date: If True, check if the schema is up to date.
|
|
553
|
+
prerender_routes: Whether to prerender routes.
|
|
539
554
|
|
|
540
555
|
Returns:
|
|
541
556
|
If the app is compiled successfully.
|
|
542
557
|
"""
|
|
543
558
|
try:
|
|
544
559
|
if compile:
|
|
545
|
-
compile_app(
|
|
560
|
+
compile_app(
|
|
561
|
+
check_if_schema_up_to_date=check_if_schema_up_to_date,
|
|
562
|
+
prerender_routes=prerender_routes,
|
|
563
|
+
)
|
|
546
564
|
else:
|
|
547
565
|
validate_app(check_if_schema_up_to_date=check_if_schema_up_to_date)
|
|
548
566
|
except Exception as e:
|
|
@@ -591,19 +609,19 @@ def get_redis_sync() -> RedisSync | None:
|
|
|
591
609
|
|
|
592
610
|
|
|
593
611
|
def parse_redis_url() -> str | None:
|
|
594
|
-
"""Parse the
|
|
612
|
+
"""Parse the REFLEX_REDIS_URL in config if applicable.
|
|
595
613
|
|
|
596
614
|
Returns:
|
|
597
615
|
If url is non-empty, return the URL as it is.
|
|
598
616
|
|
|
599
617
|
Raises:
|
|
600
|
-
ValueError: If the
|
|
618
|
+
ValueError: If the REFLEX_REDIS_URL is not a supported scheme.
|
|
601
619
|
"""
|
|
602
620
|
config = get_config()
|
|
603
621
|
if not config.redis_url:
|
|
604
622
|
return None
|
|
605
623
|
if not config.redis_url.startswith(("redis://", "rediss://", "unix://")):
|
|
606
|
-
msg = "
|
|
624
|
+
msg = "REFLEX_REDIS_URL must start with 'redis://', 'rediss://', or 'unix://'."
|
|
607
625
|
raise ValueError(msg)
|
|
608
626
|
return config.redis_url
|
|
609
627
|
|
|
@@ -720,14 +738,11 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
|
|
|
720
738
|
sys.path.insert(0, str(Path.cwd()))
|
|
721
739
|
|
|
722
740
|
config = get_config()
|
|
723
|
-
module_path =
|
|
741
|
+
module_path = get_module_path(config.module)
|
|
724
742
|
if module_path is None:
|
|
725
743
|
console.error(f"Could not find module {config.module}.")
|
|
726
744
|
raise click.exceptions.Exit(1)
|
|
727
745
|
|
|
728
|
-
if not module_path.origin:
|
|
729
|
-
console.error(f"Could not find origin for module {config.module}.")
|
|
730
|
-
raise click.exceptions.Exit(1)
|
|
731
746
|
console.info(f"Renaming app directory to {new_app_name}.")
|
|
732
747
|
process_directory(
|
|
733
748
|
Path.cwd(),
|
|
@@ -736,7 +751,7 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
|
|
|
736
751
|
exclude_dirs=[constants.Dirs.WEB, constants.Dirs.APP_ASSETS],
|
|
737
752
|
)
|
|
738
753
|
|
|
739
|
-
rename_path_up_tree(
|
|
754
|
+
rename_path_up_tree(module_path, config.app_name, new_app_name)
|
|
740
755
|
|
|
741
756
|
console.success(f"App directory renamed to [bold]{new_app_name}[/bold].")
|
|
742
757
|
|
|
@@ -1017,29 +1032,19 @@ def initialize_web_directory():
|
|
|
1017
1032
|
console.debug("Initializing the public directory.")
|
|
1018
1033
|
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
|
|
1019
1034
|
|
|
1020
|
-
console.debug("Initializing the
|
|
1021
|
-
|
|
1035
|
+
console.debug("Initializing the react-router.config.js file.")
|
|
1036
|
+
update_react_router_config()
|
|
1022
1037
|
|
|
1023
1038
|
console.debug("Initializing the reflex.json file.")
|
|
1024
1039
|
# Initialize the reflex json file.
|
|
1025
1040
|
init_reflex_json(project_hash=project_hash)
|
|
1026
1041
|
|
|
1027
1042
|
|
|
1028
|
-
@once
|
|
1029
|
-
def _turbopack_flag() -> str:
|
|
1030
|
-
return " --turbopack" if environment.REFLEX_USE_TURBOPACK.get() else ""
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
1043
|
def _compile_package_json():
|
|
1034
1044
|
return templates.PACKAGE_JSON.render(
|
|
1035
1045
|
scripts={
|
|
1036
|
-
"dev": constants.PackageJson.Commands.DEV
|
|
1037
|
-
"export": constants.PackageJson.Commands.EXPORT
|
|
1038
|
-
flags=_turbopack_flag()
|
|
1039
|
-
),
|
|
1040
|
-
"export_sitemap": constants.PackageJson.Commands.EXPORT_SITEMAP.format(
|
|
1041
|
-
flags=_turbopack_flag()
|
|
1042
|
-
),
|
|
1046
|
+
"dev": constants.PackageJson.Commands.DEV,
|
|
1047
|
+
"export": constants.PackageJson.Commands.EXPORT,
|
|
1043
1048
|
"prod": constants.PackageJson.Commands.PROD,
|
|
1044
1049
|
},
|
|
1045
1050
|
dependencies=constants.PackageJson.DEPENDENCIES,
|
|
@@ -1107,50 +1112,43 @@ def init_reflex_json(project_hash: int | None):
|
|
|
1107
1112
|
path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)
|
|
1108
1113
|
|
|
1109
1114
|
|
|
1110
|
-
def
|
|
1111
|
-
|
|
1112
|
-
):
|
|
1113
|
-
"""Update Next.js config from Reflex config.
|
|
1115
|
+
def update_react_router_config(prerender_routes: bool = False):
|
|
1116
|
+
"""Update react-router.config.js config from Reflex config.
|
|
1114
1117
|
|
|
1115
1118
|
Args:
|
|
1116
|
-
|
|
1117
|
-
transpile_packages: list of packages to transpile via next.config.js.
|
|
1119
|
+
prerender_routes: Whether to enable prerendering of routes.
|
|
1118
1120
|
"""
|
|
1119
|
-
|
|
1121
|
+
react_router_config_file_path = get_web_dir() / constants.ReactRouter.CONFIG_FILE
|
|
1120
1122
|
|
|
1121
|
-
|
|
1122
|
-
get_config(),
|
|
1123
|
+
new_react_router_config = _update_react_router_config(
|
|
1124
|
+
get_config(), prerender_routes=prerender_routes
|
|
1123
1125
|
)
|
|
1124
1126
|
|
|
1125
|
-
# Overwriting the
|
|
1127
|
+
# Overwriting the config file triggers a full server reload, so make sure
|
|
1126
1128
|
# there is actually a diff.
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1129
|
+
old_react_router_config = (
|
|
1130
|
+
react_router_config_file_path.read_text()
|
|
1131
|
+
if react_router_config_file_path.exists()
|
|
1132
|
+
else ""
|
|
1133
|
+
)
|
|
1134
|
+
if old_react_router_config != new_react_router_config:
|
|
1135
|
+
react_router_config_file_path.write_text(new_react_router_config)
|
|
1130
1136
|
|
|
1131
1137
|
|
|
1132
|
-
def
|
|
1133
|
-
|
|
1134
|
-
)
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
"
|
|
1139
|
-
"staticPageGenerationTimeout": config.static_page_generation_timeout,
|
|
1138
|
+
def _update_react_router_config(config: Config, prerender_routes: bool = False):
|
|
1139
|
+
react_router_config = {
|
|
1140
|
+
"basename": "/" + (config.frontend_path or "").removeprefix("/"),
|
|
1141
|
+
"future": {
|
|
1142
|
+
"unstable_optimizeDeps": True,
|
|
1143
|
+
},
|
|
1144
|
+
"ssr": False,
|
|
1140
1145
|
}
|
|
1141
|
-
if not config.next_dev_indicators:
|
|
1142
|
-
next_config["devIndicators"] = False
|
|
1143
1146
|
|
|
1144
|
-
if
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
)
|
|
1148
|
-
if export:
|
|
1149
|
-
next_config["output"] = "export"
|
|
1150
|
-
next_config["distDir"] = constants.Dirs.STATIC
|
|
1147
|
+
if prerender_routes:
|
|
1148
|
+
react_router_config["prerender"] = True
|
|
1149
|
+
react_router_config["build"] = constants.Dirs.BUILD_DIR
|
|
1151
1150
|
|
|
1152
|
-
|
|
1153
|
-
return f"module.exports = {next_config_json};"
|
|
1151
|
+
return f"export default {json.dumps(react_router_config)};"
|
|
1154
1152
|
|
|
1155
1153
|
|
|
1156
1154
|
def remove_existing_bun_installation():
|
reflex/utils/processes.py
CHANGED
|
@@ -4,17 +4,18 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import collections
|
|
6
6
|
import contextlib
|
|
7
|
-
import importlib.metadata
|
|
8
7
|
import os
|
|
9
8
|
import signal
|
|
9
|
+
import socket
|
|
10
10
|
import subprocess
|
|
11
11
|
from collections.abc import Callable, Generator, Sequence
|
|
12
12
|
from concurrent import futures
|
|
13
|
+
from contextlib import closing
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Any, Literal, overload
|
|
15
16
|
|
|
16
17
|
import click
|
|
17
|
-
import
|
|
18
|
+
import rich.markup
|
|
18
19
|
from redis.exceptions import RedisError
|
|
19
20
|
from rich.progress import Progress
|
|
20
21
|
|
|
@@ -52,29 +53,6 @@ def get_num_workers() -> int:
|
|
|
52
53
|
return (os.cpu_count() or 1) * 2 + 1
|
|
53
54
|
|
|
54
55
|
|
|
55
|
-
def get_process_on_port(port: int) -> psutil.Process | None:
|
|
56
|
-
"""Get the process on the given port.
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
port: The port.
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
The process on the given port.
|
|
63
|
-
"""
|
|
64
|
-
for proc in psutil.process_iter(["pid", "name", "cmdline"]):
|
|
65
|
-
with contextlib.suppress(
|
|
66
|
-
psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess
|
|
67
|
-
):
|
|
68
|
-
if importlib.metadata.version("psutil") >= "6.0.0":
|
|
69
|
-
conns = proc.net_connections(kind="inet")
|
|
70
|
-
else:
|
|
71
|
-
conns = proc.connections(kind="inet")
|
|
72
|
-
for conn in conns:
|
|
73
|
-
if conn.laddr.port == int(port):
|
|
74
|
-
return proc
|
|
75
|
-
return None
|
|
76
|
-
|
|
77
|
-
|
|
78
56
|
def is_process_on_port(port: int) -> bool:
|
|
79
57
|
"""Check if a process is running on the given port.
|
|
80
58
|
|
|
@@ -84,18 +62,16 @@ def is_process_on_port(port: int) -> bool:
|
|
|
84
62
|
Returns:
|
|
85
63
|
Whether a process is running on the given port.
|
|
86
64
|
"""
|
|
87
|
-
|
|
65
|
+
# Test IPv4 localhost (127.0.0.1)
|
|
66
|
+
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
|
|
67
|
+
ipv4_result = sock.connect_ex(("127.0.0.1", port)) == 0
|
|
88
68
|
|
|
69
|
+
# Test IPv6 localhost (::1)
|
|
70
|
+
with closing(socket.socket(socket.AF_INET6, socket.SOCK_STREAM)) as sock:
|
|
71
|
+
ipv6_result = sock.connect_ex(("::1", port)) == 0
|
|
89
72
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
port: The port.
|
|
95
|
-
"""
|
|
96
|
-
if get_process_on_port(port) is not None:
|
|
97
|
-
with contextlib.suppress(psutil.AccessDenied):
|
|
98
|
-
get_process_on_port(port).kill() # pyright: ignore [reportOptionalMemberAccess]
|
|
73
|
+
# Port is in use if either IPv4 or IPv6 is listening
|
|
74
|
+
return ipv4_result or ipv6_result
|
|
99
75
|
|
|
100
76
|
|
|
101
77
|
def change_port(port: int, _type: str) -> int:
|
|
@@ -133,13 +109,13 @@ def handle_port(service_name: str, port: int, auto_increment: bool) -> int:
|
|
|
133
109
|
Raises:
|
|
134
110
|
Exit:when the port is in use.
|
|
135
111
|
"""
|
|
136
|
-
if (
|
|
112
|
+
console.debug(f"Checking if {service_name.capitalize()} port: {port} is in use.")
|
|
113
|
+
if not is_process_on_port(port):
|
|
114
|
+
console.debug(f"{service_name.capitalize()} port: {port} is not in use.")
|
|
137
115
|
return port
|
|
138
116
|
if auto_increment:
|
|
139
117
|
return change_port(port, service_name)
|
|
140
|
-
console.error(
|
|
141
|
-
f"{service_name.capitalize()} port: {port} is already in use by PID: {process.pid}."
|
|
142
|
-
)
|
|
118
|
+
console.error(f"{service_name.capitalize()} port: {port} is already in use.")
|
|
143
119
|
raise click.exceptions.Exit
|
|
144
120
|
|
|
145
121
|
|
|
@@ -306,7 +282,7 @@ def stream_logs(
|
|
|
306
282
|
return
|
|
307
283
|
try:
|
|
308
284
|
for line in process.stdout:
|
|
309
|
-
console.debug(line, end="", progress=progress)
|
|
285
|
+
console.debug(rich.markup.escape(line), end="", progress=progress)
|
|
310
286
|
logs.append(line)
|
|
311
287
|
yield line
|
|
312
288
|
except ValueError:
|
|
@@ -320,7 +296,8 @@ def stream_logs(
|
|
|
320
296
|
|
|
321
297
|
# Windows uvicorn bug
|
|
322
298
|
# https://github.com/reflex-dev/reflex/issues/2335
|
|
323
|
-
|
|
299
|
+
# 130 is the exit code that react router returns when it is interrupted by a signal.
|
|
300
|
+
accepted_return_codes = [0, -2, 15, 130] if constants.IS_WINDOWS else [0, -2, 130]
|
|
324
301
|
if process.returncode not in accepted_return_codes and not suppress_errors:
|
|
325
302
|
console.error(f"{message} failed with exit code {process.returncode}")
|
|
326
303
|
if "".join(logs).count("CERT_HAS_EXPIRED") > 0:
|
|
@@ -412,12 +389,12 @@ def show_progress(message: str, process: subprocess.Popen, checkpoints: list[str
|
|
|
412
389
|
task = progress.add_task(f"{message}: ", total=len(checkpoints))
|
|
413
390
|
for line in stream_logs(message, process, progress=progress):
|
|
414
391
|
# Check for special strings and update the progress bar.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
392
|
+
special_string = checkpoints[0]
|
|
393
|
+
if special_string in line:
|
|
394
|
+
progress.update(task, advance=1)
|
|
395
|
+
checkpoints.pop(0)
|
|
396
|
+
if not checkpoints:
|
|
397
|
+
break
|
|
421
398
|
|
|
422
399
|
|
|
423
400
|
def atexit_handler():
|
reflex/utils/pyi_generator.py
CHANGED
|
@@ -13,7 +13,6 @@ import subprocess
|
|
|
13
13
|
import sys
|
|
14
14
|
import typing
|
|
15
15
|
from collections.abc import Callable, Iterable, Sequence
|
|
16
|
-
from fileinput import FileInput
|
|
17
16
|
from hashlib import md5
|
|
18
17
|
from inspect import getfullargspec
|
|
19
18
|
from itertools import chain
|
|
@@ -67,7 +66,6 @@ OVERWRITE_TYPES = {
|
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
DEFAULT_TYPING_IMPORTS = {
|
|
70
|
-
"overload",
|
|
71
69
|
"Any",
|
|
72
70
|
"Callable",
|
|
73
71
|
"Dict",
|
|
@@ -90,6 +88,7 @@ DEFAULT_IMPORTS = {
|
|
|
90
88
|
"EventSpec",
|
|
91
89
|
"EventType",
|
|
92
90
|
"KeyInputInfo",
|
|
91
|
+
"PointerEventInfo",
|
|
93
92
|
],
|
|
94
93
|
"reflex.style": ["Style"],
|
|
95
94
|
"reflex.vars.base": ["Var"],
|
|
@@ -366,7 +365,7 @@ def _extract_class_props_as_ast_nodes(
|
|
|
366
365
|
all_props = []
|
|
367
366
|
kwargs = []
|
|
368
367
|
for target_class in clzs:
|
|
369
|
-
event_triggers = target_class.
|
|
368
|
+
event_triggers = target_class.get_event_triggers()
|
|
370
369
|
# Import from the target class to ensure type hints are resolvable.
|
|
371
370
|
exec(f"from {target_class.__module__} import *", type_hint_globals)
|
|
372
371
|
for name, value in target_class.__annotations__.items():
|
|
@@ -409,7 +408,7 @@ def _extract_class_props_as_ast_nodes(
|
|
|
409
408
|
)
|
|
410
409
|
),
|
|
411
410
|
),
|
|
412
|
-
ast.Constant(value=default),
|
|
411
|
+
ast.Constant(value=default), # pyright: ignore [reportArgumentType]
|
|
413
412
|
)
|
|
414
413
|
)
|
|
415
414
|
return kwargs
|
|
@@ -444,7 +443,11 @@ def type_to_ast(typ: Any, cls: type) -> ast.expr:
|
|
|
444
443
|
|
|
445
444
|
if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts):
|
|
446
445
|
return ast.Name(id=typ.__name__)
|
|
447
|
-
|
|
446
|
+
if (
|
|
447
|
+
typ.__module__ in DEFAULT_IMPORTS
|
|
448
|
+
and typ.__name__ in DEFAULT_IMPORTS[typ.__module__]
|
|
449
|
+
):
|
|
450
|
+
return ast.Name(id=typ.__name__)
|
|
448
451
|
return ast.Name(id=typ.__module__ + "." + typ.__name__)
|
|
449
452
|
return ast.Name(id=typ.__name__)
|
|
450
453
|
if hasattr(typ, "_name"):
|
|
@@ -607,7 +610,7 @@ def _generate_component_create_functiondef(
|
|
|
607
610
|
return ast.Name(id=f"{' | '.join(map(ast.unparse, all_count_args_type))}")
|
|
608
611
|
return ast.Name(id="EventType[Any]")
|
|
609
612
|
|
|
610
|
-
event_triggers = clz.
|
|
613
|
+
event_triggers = clz.get_event_triggers()
|
|
611
614
|
|
|
612
615
|
# event handler kwargs
|
|
613
616
|
kwargs.extend(
|
|
@@ -672,10 +675,7 @@ def _generate_component_create_functiondef(
|
|
|
672
675
|
value=ast.Constant(value=Ellipsis),
|
|
673
676
|
),
|
|
674
677
|
],
|
|
675
|
-
decorator_list=
|
|
676
|
-
ast.Name(id="overload"),
|
|
677
|
-
*decorator_list,
|
|
678
|
-
],
|
|
678
|
+
decorator_list=list(decorator_list),
|
|
679
679
|
lineno=lineno,
|
|
680
680
|
returns=ast.Constant(value=clz.__name__),
|
|
681
681
|
)
|
|
@@ -891,7 +891,7 @@ class StubGenerator(ast.NodeTransformer):
|
|
|
891
891
|
The modified ImportFrom node.
|
|
892
892
|
"""
|
|
893
893
|
if node.module == "__future__":
|
|
894
|
-
return None # ignore __future__ imports
|
|
894
|
+
return None # ignore __future__ imports: https://docs.astral.sh/ruff/rules/future-annotations-in-stub/
|
|
895
895
|
return self.visit_Import(node)
|
|
896
896
|
|
|
897
897
|
def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef:
|
|
@@ -1104,9 +1104,10 @@ class PyiGenerator:
|
|
|
1104
1104
|
|
|
1105
1105
|
def _get_init_lazy_imports(self, mod: tuple | ModuleType, new_tree: ast.AST):
|
|
1106
1106
|
# retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present.
|
|
1107
|
-
sub_mods = getattr(mod, "_SUBMODULES", None)
|
|
1108
|
-
sub_mod_attrs
|
|
1109
|
-
|
|
1107
|
+
sub_mods: set[str] | None = getattr(mod, "_SUBMODULES", None)
|
|
1108
|
+
sub_mod_attrs: dict[str, list[str | tuple[str, str]]] | None = getattr(
|
|
1109
|
+
mod, "_SUBMOD_ATTRS", None
|
|
1110
|
+
)
|
|
1110
1111
|
|
|
1111
1112
|
if not sub_mods and not sub_mod_attrs:
|
|
1112
1113
|
return None
|
|
@@ -1114,31 +1115,34 @@ class PyiGenerator:
|
|
|
1114
1115
|
sub_mod_attrs_imports = []
|
|
1115
1116
|
|
|
1116
1117
|
if sub_mods:
|
|
1117
|
-
sub_mods_imports = [
|
|
1118
|
-
f"from . import {mod} as {mod}" for mod in sorted(sub_mods)
|
|
1119
|
-
]
|
|
1118
|
+
sub_mods_imports = [f"from . import {mod}" for mod in sorted(sub_mods)]
|
|
1120
1119
|
sub_mods_imports.append("")
|
|
1121
1120
|
|
|
1122
1121
|
if sub_mod_attrs:
|
|
1123
|
-
|
|
1124
|
-
|
|
1122
|
+
flattened_sub_mod_attrs = {
|
|
1123
|
+
imported: module
|
|
1124
|
+
for module, attrs in sub_mod_attrs.items()
|
|
1125
|
+
for imported in attrs
|
|
1125
1126
|
}
|
|
1126
1127
|
# construct the import statement and handle special cases for aliases
|
|
1127
1128
|
sub_mod_attrs_imports = [
|
|
1128
|
-
f"from .{
|
|
1129
|
+
f"from .{module} import "
|
|
1129
1130
|
+ (
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1131
|
+
(
|
|
1132
|
+
(imported[0] + " as " + imported[1])
|
|
1133
|
+
if imported[0] != imported[1]
|
|
1134
|
+
else imported[0]
|
|
1135
|
+
)
|
|
1136
|
+
if isinstance(imported, tuple)
|
|
1137
|
+
else imported
|
|
1135
1138
|
)
|
|
1136
|
-
for
|
|
1139
|
+
for imported, module in flattened_sub_mod_attrs.items()
|
|
1137
1140
|
]
|
|
1138
1141
|
sub_mod_attrs_imports.append("")
|
|
1139
1142
|
|
|
1140
1143
|
text = "\n" + "\n".join([*sub_mods_imports, *sub_mod_attrs_imports])
|
|
1141
|
-
text += ast.unparse(new_tree) + "\n"
|
|
1144
|
+
text += ast.unparse(new_tree) + "\n\n"
|
|
1145
|
+
text += f"__all__ = {getattr(mod, '__all__', [])!r}\n"
|
|
1142
1146
|
return text
|
|
1143
1147
|
|
|
1144
1148
|
def _scan_file(self, module_path: Path) -> tuple[str, str] | None:
|
|
@@ -1253,10 +1257,7 @@ class PyiGenerator:
|
|
|
1253
1257
|
if file_paths:
|
|
1254
1258
|
subprocess.run(["ruff", "format", *file_paths])
|
|
1255
1259
|
subprocess.run(["ruff", "check", "--fix", *file_paths])
|
|
1256
|
-
|
|
1257
|
-
# For some reason, we need to format the __init__.pyi files again after fixing...
|
|
1258
|
-
init_files = [f for f in file_paths if "/__init__.pyi" in f]
|
|
1259
|
-
subprocess.run(["ruff", "format", *init_files])
|
|
1260
|
+
subprocess.run(["ruff", "format", *file_paths])
|
|
1260
1261
|
|
|
1261
1262
|
if use_json:
|
|
1262
1263
|
if file_paths and changed_files is None:
|
|
@@ -1322,27 +1323,21 @@ class PyiGenerator:
|
|
|
1322
1323
|
json.dumps(pyi_hashes, indent=2, sort_keys=True) + "\n"
|
|
1323
1324
|
)
|
|
1324
1325
|
|
|
1325
|
-
# Post-process the generated pyi files to add hacky type: ignore comments
|
|
1326
|
-
for file_path in file_paths:
|
|
1327
|
-
with FileInput(file_path, inplace=True) as f:
|
|
1328
|
-
for line in f:
|
|
1329
|
-
# Hack due to ast not supporting comments in the tree.
|
|
1330
|
-
if (
|
|
1331
|
-
"def create(" in line
|
|
1332
|
-
or "Var[Figure]" in line
|
|
1333
|
-
or "Var[Template]" in line
|
|
1334
|
-
):
|
|
1335
|
-
line = line.rstrip() + " # type: ignore\n"
|
|
1336
|
-
print(line, end="") # noqa: T201
|
|
1337
|
-
|
|
1338
1326
|
|
|
1339
1327
|
if __name__ == "__main__":
|
|
1328
|
+
import argparse
|
|
1329
|
+
|
|
1330
|
+
parser = argparse.ArgumentParser(description="Generate .pyi stub files")
|
|
1331
|
+
parser.add_argument(
|
|
1332
|
+
"targets",
|
|
1333
|
+
nargs="*",
|
|
1334
|
+
default=["reflex/components", "reflex/experimental", "reflex/__init__.py"],
|
|
1335
|
+
help="Target directories/files to process",
|
|
1336
|
+
)
|
|
1337
|
+
args = parser.parse_args()
|
|
1338
|
+
|
|
1340
1339
|
logging.basicConfig(level=logging.INFO)
|
|
1341
1340
|
logging.getLogger("blib2to3.pgen2.driver").setLevel(logging.INFO)
|
|
1342
1341
|
|
|
1343
1342
|
gen = PyiGenerator()
|
|
1344
|
-
gen.scan_all(
|
|
1345
|
-
["reflex/components", "reflex/experimental", "reflex/__init__.py"],
|
|
1346
|
-
None,
|
|
1347
|
-
use_json=True,
|
|
1348
|
-
)
|
|
1343
|
+
gen.scan_all(args.targets, None, use_json=True)
|
reflex/utils/serializers.py
CHANGED
|
@@ -9,7 +9,7 @@ import functools
|
|
|
9
9
|
import inspect
|
|
10
10
|
import json
|
|
11
11
|
import warnings
|
|
12
|
-
from collections.abc import Callable, Sequence
|
|
12
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
13
13
|
from datetime import date, datetime, time, timedelta
|
|
14
14
|
from enum import Enum
|
|
15
15
|
from pathlib import Path
|
|
@@ -335,6 +335,19 @@ def serialize_sequence(value: Sequence) -> list:
|
|
|
335
335
|
return list(value)
|
|
336
336
|
|
|
337
337
|
|
|
338
|
+
@serializer(to=dict)
|
|
339
|
+
def serialize_mapping(value: Mapping) -> dict:
|
|
340
|
+
"""Serialize a mapping type to a dictionary.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
value: The mapping instance to serialize.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
A new dictionary containing the same key-value pairs as the input mapping.
|
|
347
|
+
"""
|
|
348
|
+
return {**value}
|
|
349
|
+
|
|
350
|
+
|
|
338
351
|
@serializer(to=str)
|
|
339
352
|
def serialize_datetime(dt: date | datetime | time | timedelta) -> str:
|
|
340
353
|
"""Serialize a datetime to a JSON string.
|