Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/+bundled-libraries-shim.deprecation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`reflex.components.dynamic.bundled_libraries` and `DEFAULT_BUNDLED_LIBRARIES` are deprecated (removal in 1.0) but keep working: reading either emits a deprecation warning and resolves against the active `RegistrationContext`. Use `RegistrationContext.ensure_context().bundled_libraries` to read the list, or `bundle_library()` / `reset_bundled_libraries()` to modify it.
2 changes: 1 addition & 1 deletion packages/reflex-base/news/6382.breaking.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
`get_config(reload=True)` has been replaced by `reload_config()`, and the module-level `bundled_libraries` list in `reflex_base.components.dynamic` has moved onto the active `RegistrationContext` (use `bundle_library()` / `reset_bundled_libraries()` as before).
`get_config(reload=True)` has been replaced by `reload_config()`, and the module-level `bundled_libraries` list in `reflex_base.components.dynamic` has moved onto the active `RegistrationContext` (use `bundle_library()` / `reset_bundled_libraries()` as before). Reading `reflex_base.components.dynamic.bundled_libraries` (or `DEFAULT_BUNDLED_LIBRARIES`) still works as a deprecated shim that resolves against the active context; the shims are removed in 1.0.
46 changes: 44 additions & 2 deletions packages/reflex-base/src/reflex_base/components/dynamic.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Components that are dynamically generated on the backend."""

from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING, Any, Union

from reflex_base import constants
from reflex_base.registry import RegistrationContext, _default_bundled_libraries
from reflex_base.utils import imports
from reflex_base.utils import console, imports
from reflex_base.utils.exceptions import DynamicComponentMissingLibraryError
from reflex_base.utils.format import format_library_name
from reflex_base.utils.serializers import serializer
Expand All @@ -15,6 +15,48 @@
from reflex_base.components.component import Component


def __getattr__(name: str) -> Any:
"""Provide the module-level globals that moved onto `RegistrationContext`.

Kept so downstream packages pinned to an older Reflex (notably
reflex-enterprise, which reads `dynamic.bundled_libraries`) keep working.

Args:
name: The name of the attribute to look up.

Returns:
The relocated value, resolved against the active `RegistrationContext`.

Raises:
AttributeError: If the attribute is not a relocated global.
"""
if name == "bundled_libraries":
console.deprecate(
feature_name="reflex_base.components.dynamic.bundled_libraries",
reason=(
"The bundled library list now lives on the active RegistrationContext. "
"Use RegistrationContext.ensure_context().bundled_libraries to read it, "
"or bundle_library()/reset_bundled_libraries() to modify it"
),
deprecation_version="0.9.9",
removal_version="1.0",
)
return RegistrationContext.ensure_context().bundled_libraries
if name == "DEFAULT_BUNDLED_LIBRARIES":
console.deprecate(
feature_name="reflex_base.components.dynamic.DEFAULT_BUNDLED_LIBRARIES",
reason=(
"Every RegistrationContext starts with these libraries bundled; call "
"reset_bundled_libraries() to restore them on the active context"
),
deprecation_version="0.9.9",
removal_version="1.0",
)
return _default_bundled_libraries()
msg = f"module {__name__!r} has no attribute {name!r}"
raise AttributeError(msg)


def get_cdn_url(lib: str) -> str:
"""Get the CDN URL for a library.

Expand Down
23 changes: 23 additions & 0 deletions reflex/components/dynamic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
# pyright: reportWildcardImportFromLibrary=false
"""Re-export from reflex_base."""

from typing import Any

from reflex_base.components.dynamic import * # pragma: no cover


def __getattr__(name: str) -> Any:
"""Delegate to `reflex_base.components.dynamic` for names the star import misses.

Args:
name: The name of the attribute to look up.

Returns:
The attribute from the re-exported module.

Raises:
AttributeError: If the re-exported module has no such attribute.
"""
from reflex_base.components import dynamic

try:
return getattr(dynamic, name)
except AttributeError:
msg = f"module {__name__!r} has no attribute {name!r}"
raise AttributeError(msg) from None
Empty file.
52 changes: 52 additions & 0 deletions tests/units/reflex_base/components/test_dynamic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Tests for the compatibility shims in reflex_base.components.dynamic."""

import importlib

import pytest
from reflex_base.components import dynamic
from reflex_base.registry import RegistrationContext, _default_bundled_libraries

from reflex.components import dynamic as reflex_dynamic


def test_bundled_libraries_shim_returns_active_context_list():
"""The module-level `bundled_libraries` resolves against the active context."""
with RegistrationContext() as ctx:
assert dynamic.bundled_libraries is ctx.bundled_libraries
dynamic.bundle_library("some-shimmed-lib")
assert "some-shimmed-lib" in dynamic.bundled_libraries

with RegistrationContext():
assert "some-shimmed-lib" not in dynamic.bundled_libraries


def test_bundled_libraries_shim_via_reflex_namespace():
"""reflex-enterprise reads the shim off `reflex.components.dynamic`."""
with RegistrationContext() as ctx:
assert set(reflex_dynamic.bundled_libraries) == set(ctx.bundled_libraries)


def test_default_bundled_libraries_shim():
"""The `DEFAULT_BUNDLED_LIBRARIES` shim returns the default library list."""
assert _default_bundled_libraries() == dynamic.DEFAULT_BUNDLED_LIBRARIES


def test_bundled_libraries_shim_warns(mocker):
"""Reading a relocated global emits a deprecation warning."""
deprecate = mocker.patch("reflex_base.utils.console.deprecate")

with RegistrationContext():
_ = dynamic.bundled_libraries
deprecate.assert_called_once()
assert (
deprecate.call_args.kwargs["feature_name"]
== "reflex_base.components.dynamic.bundled_libraries"
)


@pytest.mark.parametrize("module_name", ["reflex_base", "reflex"])
def test_unknown_attribute_raises(module_name: str):
"""Unknown attributes still raise AttributeError naming the module."""
module = importlib.import_module(f"{module_name}.components.dynamic")
with pytest.raises(AttributeError, match=f"{module.__name__!r}"):
_ = module.definitely_not_an_attribute
Loading