Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1f3fee598b | ||
![]() |
8df959dc43 | ||
![]() |
63e9ae869f | ||
![]() |
5a68e9d72f |
@ -25,6 +25,8 @@ def TailwindApp(
|
||||
paragraph_text: Text for the paragraph.
|
||||
paragraph_class_name: Tailwind class_name for the paragraph.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
import reflex as rx
|
||||
|
||||
class UnusedState(rx.State):
|
||||
@ -35,10 +37,15 @@ def TailwindApp(
|
||||
rx.chakra.text(paragraph_text, class_name=paragraph_class_name),
|
||||
rx.el.p(paragraph_text, class_name=paragraph_class_name),
|
||||
rx.text(paragraph_text, as_="p", class_name=paragraph_class_name),
|
||||
rx.el.div("Test external stylesheet", class_name="external"),
|
||||
id="p-content",
|
||||
)
|
||||
|
||||
app = rx.App(style={"font_family": "monospace"})
|
||||
assets = Path(__file__).resolve().parent.parent / "assets"
|
||||
assets.mkdir(exist_ok=True)
|
||||
stylesheet = assets / "test_styles.css"
|
||||
stylesheet.write_text(".external { color: rgba(0, 0, 255, 0.5) }")
|
||||
app = rx.App(style={"font_family": "monospace"}, stylesheets=[stylesheet.name])
|
||||
app.add_page(index)
|
||||
if tailwind_disabled:
|
||||
config = rx.config.get_config()
|
||||
@ -107,3 +114,9 @@ def test_tailwind_app(tailwind_app: AppHarness, tailwind_disabled: bool):
|
||||
else:
|
||||
# expect "text-red-500" from tailwind utility class
|
||||
assert p.value_of_css_property("color") in TEXT_RED_500_COLOR
|
||||
|
||||
# Assert external stylesheet is applying rules
|
||||
external = driver.find_elements(By.CLASS_NAME, "external")
|
||||
assert len(external) == 1
|
||||
for ext_div in external:
|
||||
assert ext_div.value_of_css_property("color") == "rgba(0, 0, 255, 0.5)"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "reflex"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
description = "Web apps in pure Python."
|
||||
license = "Apache-2.0"
|
||||
authors = [
|
||||
|
@ -169,7 +169,7 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
|
||||
raise FileNotFoundError(
|
||||
f"The stylesheet file {stylesheet_full_path} does not exist."
|
||||
)
|
||||
stylesheet = f"@/{stylesheet.strip('/')}"
|
||||
stylesheet = f"../{constants.Dirs.PUBLIC}/{stylesheet.strip('/')}"
|
||||
sheets.append(stylesheet) if stylesheet not in sheets else None
|
||||
return templates.STYLE.render(stylesheets=sheets)
|
||||
|
||||
|
@ -9,7 +9,7 @@ class Html(Div):
|
||||
"""Render the html.
|
||||
|
||||
Returns:
|
||||
The code to render the html component.
|
||||
The code to render the html component.
|
||||
"""
|
||||
|
||||
# The HTML to render.
|
||||
|
@ -185,7 +185,7 @@ from .elements.tables import tfoot as tfoot
|
||||
from .elements.tables import th as th
|
||||
from .elements.tables import thead as thead
|
||||
from .elements.tables import tr as tr
|
||||
from .elements.tables import Tbody as Tbody
|
||||
from .elements.tables import tbody as tbody
|
||||
from .elements.tables import Caption as Caption
|
||||
from .elements.tables import Col as Col
|
||||
from .elements.tables import Colgroup as Colgroup
|
||||
@ -195,6 +195,7 @@ from .elements.tables import Tfoot as Tfoot
|
||||
from .elements.tables import Th as Th
|
||||
from .elements.tables import Thead as Thead
|
||||
from .elements.tables import Tr as Tr
|
||||
from .elements.tables import Tbody as Tbody
|
||||
from .elements.typography import blockquote as blockquote
|
||||
from .elements.typography import dd as dd
|
||||
from .elements.typography import div as div
|
||||
|
@ -102,7 +102,7 @@ _MAPPING = {
|
||||
"th",
|
||||
"thead",
|
||||
"tr",
|
||||
"Tbody",
|
||||
"tbody",
|
||||
],
|
||||
"typography": [
|
||||
"blockquote",
|
||||
|
@ -183,7 +183,7 @@ from .tables import tfoot as tfoot
|
||||
from .tables import th as th
|
||||
from .tables import thead as thead
|
||||
from .tables import tr as tr
|
||||
from .tables import Tbody as Tbody
|
||||
from .tables import tbody as tbody
|
||||
from .tables import Caption as Caption
|
||||
from .tables import Col as Col
|
||||
from .tables import Colgroup as Colgroup
|
||||
@ -193,6 +193,7 @@ from .tables import Tfoot as Tfoot
|
||||
from .tables import Th as Th
|
||||
from .tables import Thead as Thead
|
||||
from .tables import Tr as Tr
|
||||
from .tables import Tbody as Tbody
|
||||
from .typography import blockquote as blockquote
|
||||
from .typography import dd as dd
|
||||
from .typography import div as div
|
||||
@ -316,7 +317,7 @@ _MAPPING = {
|
||||
"th",
|
||||
"thead",
|
||||
"tr",
|
||||
"Tbody",
|
||||
"tbody",
|
||||
],
|
||||
"typography": [
|
||||
"blockquote",
|
||||
|
@ -109,7 +109,7 @@ class Plotly(PlotlyLib):
|
||||
config: Var[Dict]
|
||||
|
||||
# If true, the graph will resize when the window is resized.
|
||||
use_resize_handler: Var[bool]
|
||||
use_resize_handler: Var[bool] = Var.create_safe(True)
|
||||
|
||||
# Fired after the plot is redrawn.
|
||||
on_after_plot: EventHandler[_passthrough_signature]
|
||||
|
@ -27,6 +27,8 @@ class Dirs(SimpleNamespace):
|
||||
UTILS = "utils"
|
||||
# The name of the output static directory.
|
||||
STATIC = "_static"
|
||||
# The name of the public html directory served at "/"
|
||||
PUBLIC = "public"
|
||||
# The name of the state file.
|
||||
STATE_PATH = "/".join([UTILS, "state"])
|
||||
# The name of the components file.
|
||||
@ -40,7 +42,7 @@ class Dirs(SimpleNamespace):
|
||||
# The directory where the utils file is located.
|
||||
WEB_UTILS = os.path.join(WEB, UTILS)
|
||||
# The directory where the assets are located.
|
||||
WEB_ASSETS = os.path.join(WEB, "public")
|
||||
WEB_ASSETS = os.path.join(WEB, PUBLIC)
|
||||
# The env json file.
|
||||
ENV_JSON = os.path.join(WEB, "env.json")
|
||||
# The reflex json file.
|
||||
|
@ -1,99 +0,0 @@
|
||||
"""Stub file for reflex/constants/base.py"""
|
||||
# ------------------- DO NOT EDIT ----------------------
|
||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||
# ------------------------------------------------------
|
||||
|
||||
from typing import Any, Dict, Literal, Optional, Union, overload
|
||||
from reflex.vars import Var, BaseVar, ComputedVar
|
||||
from reflex.event import EventChain, EventHandler, EventSpec
|
||||
from reflex.style import Style
|
||||
import os
|
||||
import platform
|
||||
from enum import Enum
|
||||
from importlib import metadata
|
||||
from types import SimpleNamespace
|
||||
from platformdirs import PlatformDirs
|
||||
|
||||
IS_WINDOWS = platform.system() == "Windows"
|
||||
IS_WINDOWS_BUN_SUPPORTED_MACHINE = IS_WINDOWS and platform.machine() in [
|
||||
"AMD64",
|
||||
"x86_64",
|
||||
]
|
||||
|
||||
class Dirs(SimpleNamespace):
|
||||
WEB = ".web"
|
||||
APP_ASSETS = "assets"
|
||||
EXTERNAL_APP_ASSETS = "external"
|
||||
UTILS = "utils"
|
||||
STATIC = "_static"
|
||||
STATE_PATH = "/".join([UTILS, "state"])
|
||||
COMPONENTS_PATH = "/".join([UTILS, "components"])
|
||||
CONTEXTS_PATH = "/".join([UTILS, "context"])
|
||||
WEB_PAGES = os.path.join(WEB, "pages")
|
||||
WEB_STATIC = os.path.join(WEB, STATIC)
|
||||
WEB_UTILS = os.path.join(WEB, UTILS)
|
||||
WEB_ASSETS = os.path.join(WEB, "public")
|
||||
ENV_JSON = os.path.join(WEB, "env.json")
|
||||
REFLEX_JSON = os.path.join(WEB, "reflex.json")
|
||||
POSTCSS_JS = os.path.join(WEB, "postcss.config.js")
|
||||
|
||||
class Reflex(SimpleNamespace):
|
||||
MODULE_NAME = "reflex"
|
||||
VERSION = metadata.version(MODULE_NAME)
|
||||
JSON = os.path.join(Dirs.WEB, "reflex.json")
|
||||
_dir = os.environ.get("REFLEX_DIR", "")
|
||||
DIR = _dir or PlatformDirs(MODULE_NAME, False).user_data_dir
|
||||
ROOT_DIR = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
)
|
||||
|
||||
class ReflexHostingCLI(SimpleNamespace):
|
||||
MODULE_NAME = "reflex-hosting-cli"
|
||||
|
||||
class Templates(SimpleNamespace):
|
||||
APP_TEMPLATES_ROUTE = "/app-templates"
|
||||
DEFAULT = "blank"
|
||||
|
||||
class Dirs(SimpleNamespace):
|
||||
BASE = os.path.join(Reflex.ROOT_DIR, Reflex.MODULE_NAME, ".templates")
|
||||
WEB_TEMPLATE = os.path.join(BASE, "web")
|
||||
JINJA_TEMPLATE = os.path.join(BASE, "jinja")
|
||||
CODE = "code"
|
||||
|
||||
class Next(SimpleNamespace):
|
||||
CONFIG_FILE = "next.config.js"
|
||||
SITEMAP_CONFIG_FILE = os.path.join(Dirs.WEB, "next-sitemap.config.js")
|
||||
NODE_MODULES = "node_modules"
|
||||
PACKAGE_LOCK = "package-lock.json"
|
||||
FRONTEND_LISTENING_REGEX = "Local:[\\s]+(.*)"
|
||||
|
||||
class ColorMode(SimpleNamespace):
|
||||
NAME = "colorMode"
|
||||
USE = "useColorMode"
|
||||
TOGGLE = "toggleColorMode"
|
||||
|
||||
class Env(str, Enum):
|
||||
DEV = "dev"
|
||||
PROD = "prod"
|
||||
|
||||
class LogLevel(str, Enum):
|
||||
DEBUG = "debug"
|
||||
INFO = "info"
|
||||
WARNING = "warning"
|
||||
ERROR = "error"
|
||||
CRITICAL = "critical"
|
||||
|
||||
POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000
|
||||
|
||||
class Ping(SimpleNamespace):
|
||||
INTERVAL = 25
|
||||
TIMEOUT = 120
|
||||
|
||||
COOKIES = "cookies"
|
||||
LOCAL_STORAGE = "local_storage"
|
||||
SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"
|
||||
ENV_MODE_ENV_VAR = "REFLEX_ENV_MODE"
|
||||
PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
|
||||
RELOAD_CONFIG = "__REFLEX_RELOAD_CONFIG"
|
||||
REFLEX_VAR_OPENING_TAG = "<reflex.Var>"
|
||||
REFLEX_VAR_CLOSING_TAG = "</reflex.Var>"
|
@ -9,8 +9,7 @@ import re
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
|
||||
|
||||
from reflex import constants
|
||||
from reflex.utils import exceptions, serializers, types
|
||||
from reflex.utils.serializers import serialize
|
||||
from reflex.utils import exceptions, types
|
||||
from reflex.vars import BaseVar, Var
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -400,6 +399,7 @@ def format_prop(
|
||||
"""
|
||||
# import here to avoid circular import.
|
||||
from reflex.event import EventChain
|
||||
from reflex.utils import serializers
|
||||
|
||||
try:
|
||||
# Handle var props.
|
||||
@ -687,6 +687,8 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
|
||||
Raises:
|
||||
TypeError: If the given value is not a valid state.
|
||||
"""
|
||||
from reflex.utils import serializers
|
||||
|
||||
# Handle dicts.
|
||||
if isinstance(value, dict):
|
||||
return {k: format_state(v, k) for k, v in value.items()}
|
||||
@ -700,7 +702,7 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
|
||||
return value
|
||||
|
||||
# Serialize the value.
|
||||
serialized = serialize(value)
|
||||
serialized = serializers.serialize(value)
|
||||
if serialized is not None:
|
||||
return serialized
|
||||
|
||||
@ -803,7 +805,9 @@ def json_dumps(obj: Any) -> str:
|
||||
Returns:
|
||||
A string
|
||||
"""
|
||||
return json.dumps(obj, ensure_ascii=False, default=serialize)
|
||||
from reflex.utils import serializers
|
||||
|
||||
return json.dumps(obj, ensure_ascii=False, default=serializers.serialize)
|
||||
|
||||
|
||||
def unwrap_vars(value: str) -> str:
|
||||
|
@ -12,7 +12,7 @@ from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union, get_type_
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.constants.colors import Color, format_color
|
||||
from reflex.utils import exceptions, format, types
|
||||
from reflex.utils import exceptions, types
|
||||
|
||||
# Mapping from type to a serializer.
|
||||
# The serializer should convert the type to a JSON object.
|
||||
@ -154,6 +154,8 @@ def serialize_primitive(value: Union[bool, int, float, None]) -> str:
|
||||
Returns:
|
||||
The serialized number/bool/None.
|
||||
"""
|
||||
from reflex.utils import format
|
||||
|
||||
return format.json_dumps(value)
|
||||
|
||||
|
||||
@ -180,6 +182,8 @@ def serialize_list(value: Union[List, Tuple, Set]) -> str:
|
||||
Returns:
|
||||
The serialized list.
|
||||
"""
|
||||
from reflex.utils import format
|
||||
|
||||
# Dump the list to a string.
|
||||
fprop = format.json_dumps(list(value))
|
||||
|
||||
@ -202,6 +206,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
|
||||
"""
|
||||
# Import here to avoid circular imports.
|
||||
from reflex.event import EventHandler
|
||||
from reflex.utils import format
|
||||
|
||||
prop_dict = {}
|
||||
|
||||
|
@ -42,7 +42,7 @@ from sqlalchemy.orm import (
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.utils import console, serializers
|
||||
from reflex.utils import console
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override
|
||||
@ -392,6 +392,8 @@ def is_valid_var_type(type_: Type) -> bool:
|
||||
Returns:
|
||||
Whether the type is a valid prop type.
|
||||
"""
|
||||
from reflex.utils import serializers
|
||||
|
||||
if is_union(type_):
|
||||
return all((is_valid_var_type(arg) for arg in get_args(type_)))
|
||||
return _issubclass(type_, StateVar) or serializers.has_serializer(type_)
|
||||
|
@ -35,7 +35,7 @@ from typing import (
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.utils import console, format, imports, serializers, types
|
||||
from reflex.utils import console, imports, serializers, types
|
||||
from reflex.utils.exceptions import VarAttributeError, VarTypeError, VarValueError
|
||||
|
||||
# This module used to export ImportVar itself, so we still import it for export here
|
||||
@ -364,6 +364,8 @@ class Var:
|
||||
Raises:
|
||||
VarTypeError: If the value is JSON-unserializable.
|
||||
"""
|
||||
from reflex.utils import format
|
||||
|
||||
# Check for none values.
|
||||
if value is None:
|
||||
return None
|
||||
@ -543,6 +545,8 @@ class Var:
|
||||
Returns:
|
||||
The wrapped var, i.e. {state.var}.
|
||||
"""
|
||||
from reflex.utils import format
|
||||
|
||||
out = (
|
||||
self._var_full_name
|
||||
if self._var_is_local
|
||||
@ -600,6 +604,8 @@ class Var:
|
||||
Raises:
|
||||
VarTypeError: If the var is not indexable.
|
||||
"""
|
||||
from reflex.utils import format
|
||||
|
||||
# Indexing is only supported for strings, lists, tuples, dicts, and dataframes.
|
||||
if not (
|
||||
types._issubclass(self._var_type, Union[List, Dict, Tuple, str])
|
||||
@ -793,6 +799,8 @@ class Var:
|
||||
VarTypeError: If the operation between two operands is invalid.
|
||||
VarValueError: If flip is set to true and value of operand is not provided
|
||||
"""
|
||||
from reflex.utils import format
|
||||
|
||||
if isinstance(other, str):
|
||||
other = Var.create(json.dumps(other))
|
||||
else:
|
||||
@ -1671,6 +1679,8 @@ class Var:
|
||||
Returns:
|
||||
The full name of the var.
|
||||
"""
|
||||
from reflex.utils import format
|
||||
|
||||
if not self._var_full_name_needs_state_prefix:
|
||||
return self._var_name
|
||||
return (
|
||||
@ -1690,6 +1700,8 @@ class Var:
|
||||
Returns:
|
||||
The var with the set state.
|
||||
"""
|
||||
from reflex.utils import format
|
||||
|
||||
state_name = state if isinstance(state, str) else state.get_full_name()
|
||||
new_var_data = VarData(
|
||||
state=state_name,
|
||||
|
@ -135,7 +135,7 @@ def test_compile_stylesheets(tmp_path, mocker):
|
||||
f"@import url('./tailwind.css'); \n"
|
||||
f"@import url('https://fonts.googleapis.com/css?family=Sofia&effect=neon|outline|emboss|shadow-multiple'); \n"
|
||||
f"@import url('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css'); \n"
|
||||
f"@import url('@/styles.css'); \n"
|
||||
f"@import url('../public/styles.css'); \n"
|
||||
f"@import url('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css'); \n",
|
||||
)
|
||||
|
||||
@ -166,7 +166,7 @@ def test_compile_stylesheets_exclude_tailwind(tmp_path, mocker):
|
||||
|
||||
assert compiler.compile_root_stylesheet(stylesheets) == (
|
||||
os.path.join(".web", "styles", "styles.css"),
|
||||
"@import url('@/styles.css'); \n",
|
||||
"@import url('../public/styles.css'); \n",
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user