reflex 0.7.4a2__py3-none-any.whl → 0.7.5a1__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/__init__.py +1 -0
- reflex/__init__.pyi +1 -0
- reflex/app.py +10 -8
- reflex/app_mixins/middleware.py +13 -20
- reflex/compiler/compiler.py +10 -3
- reflex/compiler/utils.py +4 -4
- reflex/components/base/app_wrap.pyi +7 -3
- reflex/components/base/body.pyi +7 -3
- reflex/components/base/document.pyi +27 -7
- reflex/components/base/error_boundary.pyi +7 -3
- reflex/components/base/fragment.pyi +7 -3
- reflex/components/base/head.pyi +12 -4
- reflex/components/base/link.pyi +12 -4
- reflex/components/base/meta.pyi +22 -6
- reflex/components/base/script.pyi +7 -3
- reflex/components/base/strict_mode.pyi +7 -3
- reflex/components/component.py +64 -23
- reflex/components/core/auto_scroll.pyi +7 -3
- reflex/components/core/banner.py +6 -2
- reflex/components/core/banner.pyi +32 -8
- reflex/components/core/client_side_routing.pyi +12 -4
- reflex/components/core/clipboard.pyi +7 -3
- reflex/components/core/debounce.pyi +7 -3
- reflex/components/core/foreach.py +5 -1
- reflex/components/core/html.pyi +7 -3
- reflex/components/core/match.py +5 -5
- reflex/components/core/sticky.pyi +21 -6
- reflex/components/core/upload.pyi +27 -7
- reflex/components/datadisplay/code.pyi +12 -4
- reflex/components/datadisplay/dataeditor.py +2 -2
- reflex/components/datadisplay/dataeditor.pyi +17 -3
- reflex/components/datadisplay/shiki_code_block.pyi +17 -4
- reflex/components/el/__init__.pyi +1 -1
- reflex/components/el/element.pyi +7 -3
- reflex/components/el/elements/__init__.py +3 -1
- reflex/components/el/elements/__init__.pyi +3 -2
- reflex/components/el/elements/base.pyi +7 -3
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/el/elements/forms.pyi +72 -16
- reflex/components/el/elements/inline.pyi +142 -30
- reflex/components/el/elements/media.pyi +127 -27
- reflex/components/el/elements/metadata.pyi +32 -8
- reflex/components/el/elements/other.pyi +37 -9
- reflex/components/el/elements/scripts.pyi +17 -5
- reflex/components/el/elements/sectioning.pyi +77 -17
- reflex/components/el/elements/tables.pyi +52 -12
- reflex/components/el/elements/typography.pyi +77 -17
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +12 -4
- reflex/components/lucide/icon.py +7 -6
- reflex/components/lucide/icon.pyi +22 -7
- reflex/components/markdown/markdown.py +1 -1
- reflex/components/markdown/markdown.pyi +7 -3
- reflex/components/moment/moment.pyi +7 -3
- reflex/components/next/base.pyi +7 -3
- reflex/components/next/image.pyi +7 -3
- reflex/components/next/link.pyi +7 -3
- reflex/components/next/video.pyi +7 -3
- reflex/components/plotly/plotly.pyi +47 -11
- reflex/components/radix/primitives/accordion.pyi +37 -9
- reflex/components/radix/primitives/base.pyi +12 -4
- reflex/components/radix/primitives/drawer.pyi +57 -13
- reflex/components/radix/primitives/form.pyi +52 -12
- reflex/components/radix/primitives/progress.pyi +27 -7
- reflex/components/radix/primitives/slider.pyi +27 -7
- reflex/components/radix/themes/base.pyi +41 -10
- reflex/components/radix/themes/color_mode.py +2 -2
- reflex/components/radix/themes/color_mode.pyi +17 -5
- reflex/components/radix/themes/components/alert_dialog.pyi +36 -9
- reflex/components/radix/themes/components/aspect_ratio.pyi +7 -3
- reflex/components/radix/themes/components/avatar.pyi +6 -3
- reflex/components/radix/themes/components/badge.pyi +6 -3
- reflex/components/radix/themes/components/button.pyi +6 -3
- reflex/components/radix/themes/components/callout.pyi +26 -7
- reflex/components/radix/themes/components/card.pyi +6 -3
- reflex/components/radix/themes/components/checkbox.pyi +16 -5
- reflex/components/radix/themes/components/checkbox_cards.pyi +11 -4
- reflex/components/radix/themes/components/checkbox_group.pyi +11 -4
- reflex/components/radix/themes/components/context_menu.pyi +66 -15
- reflex/components/radix/themes/components/data_list.pyi +21 -6
- reflex/components/radix/themes/components/dialog.pyi +36 -9
- reflex/components/radix/themes/components/dropdown_menu.pyi +41 -10
- reflex/components/radix/themes/components/hover_card.pyi +21 -6
- reflex/components/radix/themes/components/icon_button.pyi +6 -3
- reflex/components/radix/themes/components/inset.pyi +6 -3
- reflex/components/radix/themes/components/popover.pyi +21 -6
- reflex/components/radix/themes/components/progress.pyi +6 -3
- reflex/components/radix/themes/components/radio.pyi +6 -3
- reflex/components/radix/themes/components/radio_cards.pyi +11 -4
- reflex/components/radix/themes/components/radio_group.py +6 -1
- reflex/components/radix/themes/components/radio_group.pyi +21 -6
- reflex/components/radix/themes/components/scroll_area.pyi +7 -3
- reflex/components/radix/themes/components/segmented_control.pyi +11 -4
- reflex/components/radix/themes/components/select.pyi +46 -11
- reflex/components/radix/themes/components/separator.pyi +6 -3
- reflex/components/radix/themes/components/skeleton.pyi +6 -3
- reflex/components/radix/themes/components/slider.pyi +6 -3
- reflex/components/radix/themes/components/spinner.pyi +6 -3
- reflex/components/radix/themes/components/switch.pyi +6 -3
- reflex/components/radix/themes/components/table.pyi +36 -9
- reflex/components/radix/themes/components/tabs.pyi +26 -7
- reflex/components/radix/themes/components/text_area.pyi +6 -3
- reflex/components/radix/themes/components/text_field.py +3 -2
- reflex/components/radix/themes/components/text_field.pyi +16 -5
- reflex/components/radix/themes/components/tooltip.pyi +7 -3
- reflex/components/radix/themes/layout/base.pyi +6 -3
- reflex/components/radix/themes/layout/box.pyi +7 -3
- reflex/components/radix/themes/layout/center.pyi +6 -3
- reflex/components/radix/themes/layout/container.pyi +6 -3
- reflex/components/radix/themes/layout/flex.pyi +6 -3
- reflex/components/radix/themes/layout/grid.pyi +6 -3
- reflex/components/radix/themes/layout/list.pyi +27 -7
- reflex/components/radix/themes/layout/section.pyi +6 -3
- reflex/components/radix/themes/layout/spacer.pyi +6 -3
- reflex/components/radix/themes/layout/stack.pyi +16 -5
- reflex/components/radix/themes/typography/blockquote.pyi +6 -3
- reflex/components/radix/themes/typography/code.pyi +6 -3
- reflex/components/radix/themes/typography/heading.pyi +6 -3
- reflex/components/radix/themes/typography/link.pyi +6 -3
- reflex/components/radix/themes/typography/text.pyi +36 -9
- reflex/components/react_player/audio.pyi +7 -3
- reflex/components/react_player/react_player.pyi +7 -3
- reflex/components/react_player/video.pyi +7 -3
- reflex/components/recharts/cartesian.pyi +97 -21
- reflex/components/recharts/charts.pyi +62 -14
- reflex/components/recharts/general.pyi +32 -8
- reflex/components/recharts/polar.py +1 -1
- reflex/components/recharts/polar.pyi +33 -9
- reflex/components/recharts/recharts.pyi +12 -4
- reflex/components/sonner/toast.pyi +7 -2
- reflex/components/suneditor/editor.pyi +7 -3
- reflex/config.py +18 -1
- reflex/constants/installer.py +22 -1
- reflex/custom_components/custom_components.py +12 -7
- reflex/event.py +26 -10
- reflex/experimental/__init__.py +17 -6
- reflex/experimental/layout.pyi +27 -7
- reflex/model.py +3 -3
- reflex/reflex.py +33 -18
- reflex/state.py +4 -4
- reflex/style.py +2 -2
- reflex/testing.py +17 -5
- reflex/utils/console.py +2 -3
- reflex/utils/exec.py +4 -0
- reflex/utils/imports.py +14 -7
- reflex/utils/net.py +107 -18
- reflex/utils/prerequisites.py +92 -13
- reflex/utils/processes.py +52 -19
- reflex/utils/pyi_generator.py +66 -53
- reflex/utils/redir.py +3 -1
- reflex/utils/registry.py +15 -5
- reflex/utils/serializers.py +1 -2
- reflex/utils/types.py +4 -4
- reflex/vars/base.py +58 -22
- reflex/vars/number.py +23 -6
- reflex/vars/sequence.py +2 -0
- {reflex-0.7.4a2.dist-info → reflex-0.7.5a1.dist-info}/METADATA +2 -2
- {reflex-0.7.4a2.dist-info → reflex-0.7.5a1.dist-info}/RECORD +162 -162
- /reflex/{experimental → utils}/misc.py +0 -0
- {reflex-0.7.4a2.dist-info → reflex-0.7.5a1.dist-info}/WHEEL +0 -0
- {reflex-0.7.4a2.dist-info → reflex-0.7.5a1.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.4a2.dist-info → reflex-0.7.5a1.dist-info}/licenses/LICENSE +0 -0
reflex/reflex.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import atexit
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
7
8
|
|
|
8
9
|
import typer
|
|
9
10
|
import typer.core
|
|
@@ -429,7 +430,7 @@ def logout(
|
|
|
429
430
|
|
|
430
431
|
loglevel = loglevel or get_config().loglevel
|
|
431
432
|
|
|
432
|
-
logout(loglevel)
|
|
433
|
+
logout(_convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel))
|
|
433
434
|
|
|
434
435
|
|
|
435
436
|
db_cli = typer.Typer()
|
|
@@ -581,7 +582,6 @@ def deploy(
|
|
|
581
582
|
),
|
|
582
583
|
):
|
|
583
584
|
"""Deploy the app to the Reflex hosting service."""
|
|
584
|
-
from reflex_cli.constants.base import LogLevel as HostingLogLevel
|
|
585
585
|
from reflex_cli.utils import dependency
|
|
586
586
|
from reflex_cli.v2 import cli as hosting_cli
|
|
587
587
|
from reflex_cli.v2.deployments import check_version
|
|
@@ -604,21 +604,6 @@ def deploy(
|
|
|
604
604
|
# Set the log level.
|
|
605
605
|
console.set_log_level(loglevel)
|
|
606
606
|
|
|
607
|
-
def convert_reflex_loglevel_to_reflex_cli_loglevel(
|
|
608
|
-
loglevel: constants.LogLevel,
|
|
609
|
-
) -> HostingLogLevel:
|
|
610
|
-
if loglevel == constants.LogLevel.DEBUG:
|
|
611
|
-
return HostingLogLevel.DEBUG
|
|
612
|
-
if loglevel == constants.LogLevel.INFO:
|
|
613
|
-
return HostingLogLevel.INFO
|
|
614
|
-
if loglevel == constants.LogLevel.WARNING:
|
|
615
|
-
return HostingLogLevel.WARNING
|
|
616
|
-
if loglevel == constants.LogLevel.ERROR:
|
|
617
|
-
return HostingLogLevel.ERROR
|
|
618
|
-
if loglevel == constants.LogLevel.CRITICAL:
|
|
619
|
-
return HostingLogLevel.CRITICAL
|
|
620
|
-
return HostingLogLevel.INFO
|
|
621
|
-
|
|
622
607
|
# Only check requirements if interactive.
|
|
623
608
|
# There is user interaction for requirements update.
|
|
624
609
|
if interactive:
|
|
@@ -652,7 +637,7 @@ def deploy(
|
|
|
652
637
|
envfile=envfile,
|
|
653
638
|
hostname=hostname,
|
|
654
639
|
interactive=interactive,
|
|
655
|
-
loglevel=
|
|
640
|
+
loglevel=_convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel),
|
|
656
641
|
token=token,
|
|
657
642
|
project=project,
|
|
658
643
|
project_name=project_name,
|
|
@@ -676,6 +661,36 @@ def rename(
|
|
|
676
661
|
prerequisites.rename_app(new_name, loglevel)
|
|
677
662
|
|
|
678
663
|
|
|
664
|
+
if TYPE_CHECKING:
|
|
665
|
+
from reflex_cli.constants.base import LogLevel as HostingLogLevel
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _convert_reflex_loglevel_to_reflex_cli_loglevel(
|
|
669
|
+
loglevel: constants.LogLevel,
|
|
670
|
+
) -> HostingLogLevel:
|
|
671
|
+
"""Convert a Reflex log level to a Reflex CLI log level.
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
loglevel: The Reflex log level to convert.
|
|
675
|
+
|
|
676
|
+
Returns:
|
|
677
|
+
The converted Reflex CLI log level.
|
|
678
|
+
"""
|
|
679
|
+
from reflex_cli.constants.base import LogLevel as HostingLogLevel
|
|
680
|
+
|
|
681
|
+
if loglevel == constants.LogLevel.DEBUG:
|
|
682
|
+
return HostingLogLevel.DEBUG
|
|
683
|
+
if loglevel == constants.LogLevel.INFO:
|
|
684
|
+
return HostingLogLevel.INFO
|
|
685
|
+
if loglevel == constants.LogLevel.WARNING:
|
|
686
|
+
return HostingLogLevel.WARNING
|
|
687
|
+
if loglevel == constants.LogLevel.ERROR:
|
|
688
|
+
return HostingLogLevel.ERROR
|
|
689
|
+
if loglevel == constants.LogLevel.CRITICAL:
|
|
690
|
+
return HostingLogLevel.CRITICAL
|
|
691
|
+
return HostingLogLevel.INFO
|
|
692
|
+
|
|
693
|
+
|
|
679
694
|
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
|
|
680
695
|
cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
|
|
681
696
|
cli.add_typer(
|
reflex/state.py
CHANGED
|
@@ -593,8 +593,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
593
593
|
if cls._item_is_event_handler(name, fn)
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
-
for
|
|
597
|
-
for name, value in
|
|
596
|
+
for mixin_cls in cls._mixins():
|
|
597
|
+
for name, value in mixin_cls.__dict__.items():
|
|
598
598
|
if name in cls.inherited_vars:
|
|
599
599
|
continue
|
|
600
600
|
if is_computed_var(value):
|
|
@@ -605,7 +605,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
605
605
|
cls.computed_vars[newcv._js_expr] = newcv
|
|
606
606
|
cls.vars[newcv._js_expr] = newcv
|
|
607
607
|
continue
|
|
608
|
-
if types.is_backend_base_variable(name,
|
|
608
|
+
if types.is_backend_base_variable(name, mixin_cls):
|
|
609
609
|
cls.backend_vars[name] = copy.deepcopy(value)
|
|
610
610
|
continue
|
|
611
611
|
if events.get(name) is not None:
|
|
@@ -907,7 +907,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
907
907
|
raise ValueError(f"Only one parent state is allowed {parent_states}.")
|
|
908
908
|
# The first non-mixin state in the mro is our parent.
|
|
909
909
|
for base in cls.mro()[1:]:
|
|
910
|
-
if
|
|
910
|
+
if not issubclass(base, BaseState) or base._mixin:
|
|
911
911
|
continue
|
|
912
912
|
if base is BaseState:
|
|
913
913
|
break
|
reflex/style.py
CHANGED
|
@@ -237,10 +237,10 @@ def format_style_key(key: str) -> tuple[str, ...]:
|
|
|
237
237
|
EMPTY_VAR_DATA = VarData()
|
|
238
238
|
|
|
239
239
|
|
|
240
|
-
class Style(dict):
|
|
240
|
+
class Style(dict[str, Any]):
|
|
241
241
|
"""A style dictionary."""
|
|
242
242
|
|
|
243
|
-
def __init__(self, style_dict: dict | None = None, **kwargs):
|
|
243
|
+
def __init__(self, style_dict: dict[str, Any] | None = None, **kwargs):
|
|
244
244
|
"""Initialize the style.
|
|
245
245
|
|
|
246
246
|
Args:
|
reflex/testing.py
CHANGED
|
@@ -27,6 +27,7 @@ from typing import (
|
|
|
27
27
|
Callable,
|
|
28
28
|
Coroutine,
|
|
29
29
|
Optional,
|
|
30
|
+
Sequence,
|
|
30
31
|
Type,
|
|
31
32
|
TypeVar,
|
|
32
33
|
)
|
|
@@ -268,6 +269,10 @@ class AppHarness:
|
|
|
268
269
|
loglevel=reflex.constants.LogLevel.INFO,
|
|
269
270
|
)
|
|
270
271
|
self.app_module_path.write_text(source_code)
|
|
272
|
+
else:
|
|
273
|
+
# Just initialize the web folder.
|
|
274
|
+
with chdir(self.app_path):
|
|
275
|
+
reflex.utils.prerequisites.initialize_frontend_dependencies()
|
|
271
276
|
with chdir(self.app_path):
|
|
272
277
|
# ensure config and app are reloaded when testing different app
|
|
273
278
|
reflex.config.get_config(reload=True)
|
|
@@ -290,8 +295,10 @@ class AppHarness:
|
|
|
290
295
|
if self.app_instance and isinstance(
|
|
291
296
|
self.app_instance._state_manager, StateManagerRedis
|
|
292
297
|
):
|
|
298
|
+
if self.app_instance._state is None:
|
|
299
|
+
raise RuntimeError("State is not set.")
|
|
293
300
|
# Create our own redis connection for testing.
|
|
294
|
-
self.state_manager = StateManagerRedis.create(self.app_instance._state)
|
|
301
|
+
self.state_manager = StateManagerRedis.create(self.app_instance._state)
|
|
295
302
|
else:
|
|
296
303
|
self.state_manager = (
|
|
297
304
|
self.app_instance._state_manager if self.app_instance else None
|
|
@@ -455,6 +462,10 @@ class AppHarness:
|
|
|
455
462
|
|
|
456
463
|
def stop(self) -> None:
|
|
457
464
|
"""Stop the frontend and backend servers."""
|
|
465
|
+
# Quit browsers first to avoid any lingering events being sent during shutdown.
|
|
466
|
+
for driver in self._frontends:
|
|
467
|
+
driver.quit()
|
|
468
|
+
|
|
458
469
|
self._reload_state_module()
|
|
459
470
|
|
|
460
471
|
if self.backend is not None:
|
|
@@ -485,8 +496,6 @@ class AppHarness:
|
|
|
485
496
|
self.backend_thread.join()
|
|
486
497
|
if self.frontend_output_thread is not None:
|
|
487
498
|
self.frontend_output_thread.join()
|
|
488
|
-
for driver in self._frontends:
|
|
489
|
-
driver.quit()
|
|
490
499
|
|
|
491
500
|
# Cleanup decorated pages added during testing
|
|
492
501
|
for page in self._decorated_pages:
|
|
@@ -764,7 +773,7 @@ class AppHarness:
|
|
|
764
773
|
self,
|
|
765
774
|
element: "WebElement",
|
|
766
775
|
timeout: TimeoutType = None,
|
|
767
|
-
exp_not_equal: str = "",
|
|
776
|
+
exp_not_equal: str | Sequence[str] = "",
|
|
768
777
|
) -> str | None:
|
|
769
778
|
"""Poll element.get_attribute("value") for change.
|
|
770
779
|
|
|
@@ -779,8 +788,11 @@ class AppHarness:
|
|
|
779
788
|
Raises:
|
|
780
789
|
TimeoutError: when the timeout expires before value changes
|
|
781
790
|
"""
|
|
791
|
+
exp_not_equal = (
|
|
792
|
+
(exp_not_equal,) if isinstance(exp_not_equal, str) else exp_not_equal
|
|
793
|
+
)
|
|
782
794
|
if not self._poll_for(
|
|
783
|
-
target=lambda: element.get_attribute("value")
|
|
795
|
+
target=lambda: element.get_attribute("value") not in exp_not_equal,
|
|
784
796
|
timeout=timeout,
|
|
785
797
|
):
|
|
786
798
|
raise TimeoutError(
|
reflex/utils/console.py
CHANGED
|
@@ -201,10 +201,9 @@ def _get_first_non_framework_frame() -> FrameType | None:
|
|
|
201
201
|
# Exclude utility modules that should never be the source of deprecated reflex usage.
|
|
202
202
|
exclude_modules = [click, rx, typer, typing_extensions]
|
|
203
203
|
exclude_roots = [
|
|
204
|
-
p.parent.resolve()
|
|
205
|
-
if (p := Path(m.__file__)).name == "__init__.py" # pyright: ignore [reportArgumentType]
|
|
206
|
-
else p.resolve()
|
|
204
|
+
p.parent.resolve() if (p := Path(file)).name == "__init__.py" else p.resolve()
|
|
207
205
|
for m in exclude_modules
|
|
206
|
+
if (file := m.__file__)
|
|
208
207
|
]
|
|
209
208
|
# Specifically exclude the reflex cli module.
|
|
210
209
|
if reflex_bin := shutil.which(b"reflex"):
|
reflex/utils/exec.py
CHANGED
|
@@ -330,6 +330,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
330
330
|
log_level=loglevel.value,
|
|
331
331
|
reload=True,
|
|
332
332
|
reload_dirs=list(map(str, get_reload_paths())),
|
|
333
|
+
reload_delay=0.1,
|
|
333
334
|
)
|
|
334
335
|
|
|
335
336
|
|
|
@@ -356,6 +357,9 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
356
357
|
log_level=LogLevels(loglevel.value),
|
|
357
358
|
reload=True,
|
|
358
359
|
reload_paths=get_reload_paths(),
|
|
360
|
+
reload_ignore_worker_failure=True,
|
|
361
|
+
reload_tick=100,
|
|
362
|
+
workers_kill_timeout=2,
|
|
359
363
|
).serve()
|
|
360
364
|
|
|
361
365
|
|
reflex/utils/imports.py
CHANGED
|
@@ -4,11 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import dataclasses
|
|
6
6
|
from collections import defaultdict
|
|
7
|
-
from typing import DefaultDict, Union
|
|
7
|
+
from typing import DefaultDict, Mapping, Sequence, Union
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def merge_imports(
|
|
11
|
-
*imports: ImportDict | ParsedImportDict |
|
|
11
|
+
*imports: ImportDict | ParsedImportDict | ParsedImportTuple,
|
|
12
12
|
) -> ParsedImportDict:
|
|
13
13
|
"""Merge multiple import dicts together.
|
|
14
14
|
|
|
@@ -43,7 +43,9 @@ def merge_imports(
|
|
|
43
43
|
return all_imports
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def parse_imports(
|
|
46
|
+
def parse_imports(
|
|
47
|
+
imports: ImmutableImportDict | ImmutableParsedImportDict,
|
|
48
|
+
) -> ParsedImportDict:
|
|
47
49
|
"""Parse the import dict into a standard format.
|
|
48
50
|
|
|
49
51
|
Args:
|
|
@@ -53,10 +55,12 @@ def parse_imports(imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
|
|
|
53
55
|
The parsed import dict.
|
|
54
56
|
"""
|
|
55
57
|
|
|
56
|
-
def _make_list(
|
|
58
|
+
def _make_list(
|
|
59
|
+
value: ImmutableImportTypes,
|
|
60
|
+
) -> list[str | ImportVar] | list[ImportVar]:
|
|
57
61
|
if isinstance(value, (str, ImportVar)):
|
|
58
62
|
return [value]
|
|
59
|
-
return value
|
|
63
|
+
return list(value)
|
|
60
64
|
|
|
61
65
|
return {
|
|
62
66
|
package: [
|
|
@@ -68,7 +72,7 @@ def parse_imports(imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
|
|
|
68
72
|
|
|
69
73
|
|
|
70
74
|
def collapse_imports(
|
|
71
|
-
imports: ParsedImportDict |
|
|
75
|
+
imports: ParsedImportDict | ParsedImportTuple,
|
|
72
76
|
) -> ParsedImportDict:
|
|
73
77
|
"""Remove all duplicate ImportVar within an ImportDict.
|
|
74
78
|
|
|
@@ -132,6 +136,9 @@ class ImportVar:
|
|
|
132
136
|
|
|
133
137
|
|
|
134
138
|
ImportTypes = Union[str, ImportVar, list[str | ImportVar], list[ImportVar]]
|
|
139
|
+
ImmutableImportTypes = Union[str, ImportVar, Sequence[str | ImportVar]]
|
|
135
140
|
ImportDict = dict[str, ImportTypes]
|
|
141
|
+
ImmutableImportDict = Mapping[str, ImmutableImportTypes]
|
|
136
142
|
ParsedImportDict = dict[str, list[ImportVar]]
|
|
137
|
-
ImmutableParsedImportDict =
|
|
143
|
+
ImmutableParsedImportDict = Mapping[str, Sequence[ImportVar]]
|
|
144
|
+
ParsedImportTuple = tuple[tuple[str, tuple[ImportVar, ...]], ...]
|
reflex/utils/net.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"""Helpers for downloading files from the network."""
|
|
2
2
|
|
|
3
|
+
import functools
|
|
4
|
+
import time
|
|
5
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
6
|
+
|
|
3
7
|
import httpx
|
|
4
8
|
|
|
5
|
-
from
|
|
9
|
+
from reflex.utils.decorator import once
|
|
10
|
+
|
|
6
11
|
from . import console
|
|
7
12
|
|
|
8
13
|
|
|
@@ -12,30 +17,114 @@ def _httpx_verify_kwarg() -> bool:
|
|
|
12
17
|
Returns:
|
|
13
18
|
True if SSL verification is enabled, False otherwise
|
|
14
19
|
"""
|
|
20
|
+
from ..config import environment
|
|
21
|
+
|
|
15
22
|
return not environment.SSL_NO_VERIFY.get()
|
|
16
23
|
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
_P = ParamSpec("_P")
|
|
26
|
+
_T = TypeVar("_T")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _wrap_https_func(
|
|
30
|
+
func: Callable[_P, _T],
|
|
31
|
+
) -> Callable[_P, _T]:
|
|
32
|
+
"""Wrap an HTTPS function with logging.
|
|
20
33
|
|
|
21
34
|
Args:
|
|
22
|
-
|
|
23
|
-
**kwargs: Additional keyword arguments to pass to httpx.get.
|
|
35
|
+
func: The function to wrap.
|
|
24
36
|
|
|
25
37
|
Returns:
|
|
26
|
-
The
|
|
38
|
+
The wrapped function.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@functools.wraps(func)
|
|
42
|
+
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
|
|
43
|
+
url = args[0]
|
|
44
|
+
console.debug(f"Sending HTTPS request to {args[0]}")
|
|
45
|
+
initial_time = time.time()
|
|
46
|
+
try:
|
|
47
|
+
response = func(*args, **kwargs)
|
|
48
|
+
except httpx.ConnectError as err:
|
|
49
|
+
if "CERTIFICATE_VERIFY_FAILED" in str(err):
|
|
50
|
+
# If the error is a certificate verification error, recommend mitigating steps.
|
|
51
|
+
console.error(
|
|
52
|
+
f"Certificate verification failed for {url}. Set environment variable SSL_CERT_FILE to the "
|
|
53
|
+
"path of the certificate file or SSL_NO_VERIFY=1 to disable verification."
|
|
54
|
+
)
|
|
55
|
+
raise
|
|
56
|
+
else:
|
|
57
|
+
console.debug(
|
|
58
|
+
f"Received response from {url} in {time.time() - initial_time:.3f} seconds"
|
|
59
|
+
)
|
|
60
|
+
return response
|
|
61
|
+
|
|
62
|
+
return wrapper
|
|
63
|
+
|
|
27
64
|
|
|
28
|
-
|
|
29
|
-
|
|
65
|
+
def _is_ipv4_supported() -> bool:
|
|
66
|
+
"""Determine if the system supports IPv4.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if the system supports IPv4, False otherwise.
|
|
30
70
|
"""
|
|
31
|
-
kwargs.setdefault("verify", _httpx_verify_kwarg())
|
|
32
71
|
try:
|
|
33
|
-
|
|
34
|
-
except httpx.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
httpx.head("http://1.1.1.1", timeout=3)
|
|
73
|
+
except httpx.RequestError:
|
|
74
|
+
return False
|
|
75
|
+
else:
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _is_ipv6_supported() -> bool:
|
|
80
|
+
"""Determine if the system supports IPv6.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if the system supports IPv6, False otherwise.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
httpx.head("http://[2606:4700:4700::1111]", timeout=3)
|
|
87
|
+
except httpx.RequestError:
|
|
88
|
+
return False
|
|
89
|
+
else:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _should_use_ipv6() -> bool:
|
|
94
|
+
"""Determine if the system supports IPv6.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
True if the system supports IPv6, False otherwise.
|
|
98
|
+
"""
|
|
99
|
+
return not _is_ipv4_supported() and _is_ipv6_supported()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _httpx_local_address_kwarg() -> str:
|
|
103
|
+
"""Get the value of the HTTPX local_address keyword argument.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The local address to bind to
|
|
107
|
+
"""
|
|
108
|
+
from ..config import environment
|
|
109
|
+
|
|
110
|
+
return environment.REFLEX_HTTP_CLIENT_BIND_ADDRESS.get() or (
|
|
111
|
+
"::" if _should_use_ipv6() else "0.0.0.0"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@once
|
|
116
|
+
def _httpx_client() -> httpx.Client:
|
|
117
|
+
"""Get an HTTPX client.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
An HTTPX client.
|
|
121
|
+
"""
|
|
122
|
+
return httpx.Client(
|
|
123
|
+
transport=httpx.HTTPTransport(
|
|
124
|
+
local_address=_httpx_local_address_kwarg(),
|
|
125
|
+
verify=_httpx_verify_kwarg(),
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
get = _wrap_https_func(_httpx_client().get)
|