Skip to content

Commit

Permalink
Merge branch 'main' into lendemor/fix_bun_path
Browse files Browse the repository at this point in the history
  • Loading branch information
Lendemor committed Feb 10, 2025
2 parents 0834176 + 8b2c729 commit 054a5ff
Show file tree
Hide file tree
Showing 30 changed files with 1,471 additions and 163 deletions.
66 changes: 45 additions & 21 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,15 @@
_substate_key,
code_uses_state_contexts,
)
from reflex.utils import codespaces, console, exceptions, format, prerequisites, types
from reflex.utils import (
codespaces,
console,
exceptions,
format,
path_ops,
prerequisites,
types,
)
from reflex.utils.exec import is_prod_mode, is_testing_env
from reflex.utils.imports import ImportVar

Expand Down Expand Up @@ -991,9 +999,10 @@ def get_compilation_time() -> str:
should_compile = self._should_compile()

if not should_compile:
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)
with console.timing("Evaluate Pages (Backend)"):
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)

# Add the optional endpoints (_upload)
self._add_optional_endpoints()
Expand All @@ -1019,10 +1028,11 @@ def get_compilation_time() -> str:
+ adhoc_steps_without_executor,
)

for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)
progress.advance(task)
with console.timing("Evaluate Pages (Frontend)"):
for route in self._unevaluated_pages:
console.debug(f"Evaluating page: {route}")
self._compile_page(route, save_page=should_compile)
progress.advance(task)

# Add the optional endpoints (_upload)
self._add_optional_endpoints()
Expand Down Expand Up @@ -1057,13 +1067,13 @@ def get_compilation_time() -> str:
custom_components |= component._get_all_custom_components()

# Perform auto-memoization of stateful components.
(
stateful_components_path,
stateful_components_code,
page_components,
) = compiler.compile_stateful_components(self._pages.values())

progress.advance(task)
with console.timing("Auto-memoize StatefulComponents"):
(
stateful_components_path,
stateful_components_code,
page_components,
) = compiler.compile_stateful_components(self._pages.values())
progress.advance(task)

# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
if code_uses_state_contexts(stateful_components_code) and self._state is None:
Expand All @@ -1086,6 +1096,17 @@ def get_compilation_time() -> str:

progress.advance(task)

# Copy the assets.
assets_src = Path.cwd() / constants.Dirs.APP_ASSETS
if assets_src.is_dir():
with console.timing("Copy assets"):
path_ops.update_directory_tree(
src=assets_src,
dest=(
Path.cwd() / prerequisites.get_web_dir() / constants.Dirs.PUBLIC
),
)

# Use a forking process pool, if possible. Much faster, especially for large sites.
# Fallback to ThreadPoolExecutor as something that will always work.
executor = None
Expand Down Expand Up @@ -1138,9 +1159,10 @@ def _submit_work(fn: Callable, *args, **kwargs):
_submit_work(compiler.remove_tailwind_from_postcss)

# Wait for all compilation tasks to complete.
for future in concurrent.futures.as_completed(result_futures):
compile_results.append(future.result())
progress.advance(task)
with console.timing("Compile to Javascript"):
for future in concurrent.futures.as_completed(result_futures):
compile_results.append(future.result())
progress.advance(task)

app_root = self._app_root(app_wrappers=app_wrappers)

Expand Down Expand Up @@ -1175,7 +1197,8 @@ def _submit_work(fn: Callable, *args, **kwargs):
progress.stop()

# Install frontend packages.
self._get_frontend_packages(all_imports)
with console.timing("Install Frontend Packages"):
self._get_frontend_packages(all_imports)

# Setup the next.config.js
transpile_packages = [
Expand All @@ -1201,8 +1224,9 @@ def _submit_work(fn: Callable, *args, **kwargs):
# Remove pages that are no longer in the app.
p.unlink()

for output_path, code in compile_results:
compiler_utils.write_page(output_path, code)
with console.timing("Write to Disk"):
for output_path, code in compile_results:
compiler_utils.write_page(output_path, code)

@contextlib.asynccontextmanager
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
Expand Down
34 changes: 22 additions & 12 deletions reflex/compiler/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,24 +119,34 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
validate_imports(collapsed_import_dict)
import_dicts = []
for lib, fields in collapsed_import_dict.items():
default, rest = compile_import_statement(fields)

# prevent lib from being rendered on the page if all imports are non rendered kind
if not any(f.render for f in fields):
continue

if not lib:
if default:
raise ValueError("No default field allowed for empty library.")
if rest is None or len(rest) == 0:
raise ValueError("No fields to import.")
import_dicts.extend(get_import_dict(module) for module in sorted(rest))
continue
lib_paths: dict[str, list[ImportVar]] = {}

for field in fields:
lib_paths.setdefault(field.package_path, []).append(field)

# remove the version before rendering the package imports
lib = format.format_library_name(lib)
compiled = {
path: compile_import_statement(fields) for path, fields in lib_paths.items()
}

for path, (default, rest) in compiled.items():
if not lib:
if default:
raise ValueError("No default field allowed for empty library.")
if rest is None or len(rest) == 0:
raise ValueError("No fields to import.")
import_dicts.extend(get_import_dict(module) for module in sorted(rest))
continue

# remove the version before rendering the package imports
formatted_lib = format.format_library_name(lib) + (
path if path != "/" else ""
)

import_dicts.append(get_import_dict(lib, default, rest))
import_dicts.append(get_import_dict(formatted_lib, default, rest))
return import_dicts


Expand Down
15 changes: 5 additions & 10 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def evaluate_style_namespaces(style: ComponentStyle) -> dict:
Union[str, Type[BaseComponent], Callable, ComponentNamespace], Any
]
ComponentChild = Union[types.PrimitiveType, Var, BaseComponent]
ComponentChildTypes = (*types.PrimitiveTypes, Var, BaseComponent)


def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
Expand All @@ -191,11 +192,7 @@ def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
Returns:
Whether the object satisfies the type hint.
"""
if isinstance(obj, LiteralVar):
return types._isinstance(obj._var_value, type_hint)
if isinstance(obj, Var):
return types._issubclass(obj._var_type, type_hint)
return types._isinstance(obj, type_hint)
return types._isinstance(obj, type_hint, nested=1)


class Component(BaseComponent, ABC):
Expand Down Expand Up @@ -712,8 +709,8 @@ def validate_children(children: tuple | list):
validate_children(child)

# Make sure the child is a valid type.
if isinstance(child, dict) or not types._isinstance(
child, ComponentChild
if isinstance(child, dict) or not isinstance(
child, ComponentChildTypes
):
raise ChildrenTypeError(component=cls.__name__, child=child)

Expand Down Expand Up @@ -1771,9 +1768,7 @@ def get_prop_vars(self) -> List[Var]:
return [
Var(
_js_expr=name,
_var_type=(
prop._var_type if types._isinstance(prop, Var) else type(prop)
),
_var_type=(prop._var_type if isinstance(prop, Var) else type(prop)),
).guess_type()
for name, prop in self.props.items()
]
Expand Down
14 changes: 12 additions & 2 deletions reflex/components/core/foreach.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ def create(
TypeError: If the render function is a ComponentState.
UntypedVarError: If the iterable is of type Any without a type annotation.
"""
from reflex.vars.object import ObjectVar
from reflex.vars import ArrayVar, ObjectVar, StringVar

iterable = LiteralVar.create(iterable).guess_type()

iterable = LiteralVar.create(iterable)
if iterable._var_type == Any:
raise ForeachVarError(
f"Could not foreach over var `{iterable!s}` of type Any. "
Expand All @@ -75,6 +76,15 @@ def create(
if isinstance(iterable, ObjectVar):
iterable = iterable.entries()

if isinstance(iterable, StringVar):
iterable = iterable.split()

if not isinstance(iterable, ArrayVar):
raise ForeachVarError(
f"Could not foreach over var `{iterable!s}` of type {iterable._var_type}. "
"See https://reflex.dev/docs/library/dynamic-rendering/foreach/"
)

component = cls(
iterable=iterable,
render_fn=render_fn,
Expand Down
8 changes: 4 additions & 4 deletions reflex/components/core/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ def _validate_return_types(cls, match_cases: List[List[Var]]) -> None:
first_case_return = match_cases[0][-1]
return_type = type(first_case_return)

if types._isinstance(first_case_return, BaseComponent):
if isinstance(first_case_return, BaseComponent):
return_type = BaseComponent
elif types._isinstance(first_case_return, Var):
elif isinstance(first_case_return, Var):
return_type = Var

for index, case in enumerate(match_cases):
Expand Down Expand Up @@ -228,8 +228,8 @@ def _create_match_cond_var_or_component(

# Validate the match cases (as well as the default case) to have Var return types.
if any(
case for case in match_cases if not types._isinstance(case[-1], Var)
) or not types._isinstance(default, Var):
case for case in match_cases if not isinstance(case[-1], Var)
) or not isinstance(default, Var):
raise ValueError("Return types of match cases should be Vars.")

return Var(
Expand Down
4 changes: 1 addition & 3 deletions reflex/components/core/sticky.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ def add_style(self):
default=True,
global_ref=False,
)
localhost_hostnames = Var.create(
["localhost", "127.0.0.1", "[::1]"]
).guess_type()
localhost_hostnames = Var.create(["localhost", "127.0.0.1", "[::1]"])
is_localhost_expr = localhost_hostnames.contains(
Var("window.location.hostname", _var_type=str).guess_type(),
)
Expand Down
46 changes: 36 additions & 10 deletions reflex/components/datadisplay/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import dataclasses
import typing
from typing import ClassVar, Dict, Literal, Optional, Union

from reflex.components.component import Component, ComponentNamespace
Expand Down Expand Up @@ -503,7 +504,7 @@ def _exclude_props(self) -> list[str]:
return ["can_copy", "copy_button"]

@classmethod
def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> str:
def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> Var:
"""Get the hook to register the language.
Args:
Expand All @@ -514,21 +515,46 @@ def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> str:
Returns:
The hook to register the language.
"""
return f"""
if ({language_var!s}) {{
(async () => {{
try {{
language_in_there = Var.create(typing.get_args(LiteralCodeLanguage)).contains(
language_var
)
async_load = f"""
(async () => {{
try {{
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`);
SyntaxHighlighter.registerLanguage({language_var!s}, module.default);
}} catch (error) {{
console.error(`Error importing language module for ${{{language_var!s}}}:`, error);
}}
}})();
}} catch (error) {{
console.error(`Language ${{{language_var!s}}} is not supported for code blocks inside of markdown: `, error);
}}
}})();
"""
return Var(
f"""
if ({language_var!s}) {{
if (!{language_in_there!s}) {{
console.warn(`Language \\`${{{language_var!s}}}\\` is not supported for code blocks inside of markdown.`);
{language_var!s} = '';
}} else {{
{async_load!s}
}}
}}
"""
if not isinstance(language_var, LiteralVar)
else f"""
if ({language_var!s}) {{
{async_load!s}
}}""",
_var_data=VarData(
imports={
cls.__fields__["library"].default: [
ImportVar(tag="PrismAsyncLight", alias="SyntaxHighlighter")
]
},
),
)

@classmethod
def get_component_map_custom_code(cls) -> str:
def get_component_map_custom_code(cls) -> Var:
"""Get the custom code for the component.
Returns:
Expand Down
2 changes: 1 addition & 1 deletion reflex/components/datadisplay/code.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ class CodeBlock(Component, MarkdownComponentMap):

def add_style(self): ...
@classmethod
def get_component_map_custom_code(cls) -> str: ...
def get_component_map_custom_code(cls) -> Var: ...
def add_hooks(self) -> list[str | Var]: ...

class CodeblockNamespace(ComponentNamespace):
Expand Down
14 changes: 11 additions & 3 deletions reflex/components/lucide/icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from reflex.utils import format
from reflex.utils.imports import ImportVar
from reflex.vars.base import LiteralVar, Var
from reflex.vars.sequence import LiteralStringVar
from reflex.vars.sequence import LiteralStringVar, StringVar


class LucideIconComponent(Component):
Expand Down Expand Up @@ -40,7 +40,12 @@ def create(cls, *children, **props) -> Component:
The created component.
"""
if children:
if len(children) == 1 and isinstance(children[0], str):
if len(children) == 1:
child = Var.create(children[0]).guess_type()
if not isinstance(child, StringVar):
raise AttributeError(
f"Icon name must be a string, got {children[0]._var_type if isinstance(children[0], Var) else children[0]}"
)
props["tag"] = children[0]
else:
raise AttributeError(
Expand All @@ -56,7 +61,10 @@ def create(cls, *children, **props) -> Component:
else:
raise TypeError(f"Icon name must be a string, got {type(tag)}")
elif isinstance(tag, Var):
return DynamicIcon.create(name=tag, **props)
tag_stringified = tag.guess_type()
if not isinstance(tag_stringified, StringVar):
raise TypeError(f"Icon name must be a string, got {tag._var_type}")
return DynamicIcon.create(name=tag_stringified.replace("_", "-"), **props)

if (
not isinstance(tag, str)
Expand Down
Loading

0 comments on commit 054a5ff

Please sign in to comment.