Skip to content

Commit

Permalink
module components should be accessible as attrs
Browse files Browse the repository at this point in the history
This is just more intuitive that declaring them up-front
  • Loading branch information
rmorshea committed Jan 5, 2021
1 parent 6d16785 commit 9aa5183
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 40 deletions.
5 changes: 2 additions & 3 deletions docs/source/examples/material_ui_button_no_action.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import idom

material_ui = idom.install("@material-ui/core")
MaterialButton = material_ui.define("Button", fallback="loading...")
material_ui = idom.install("@material-ui/core", fallback="loading...")

idom.run(
idom.element(
lambda: MaterialButton(
lambda: material_ui.Button(
{"color": "primary", "variant": "contained"}, "Hello World!"
)
)
Expand Down
5 changes: 2 additions & 3 deletions docs/source/examples/material_ui_button_on_click.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
import idom


material_ui = idom.install("@material-ui/core")
MaterialButton = material_ui.define("Button", fallback="loading...")
material_ui = idom.install("@material-ui/core", fallback="loading...")


@idom.element
def ViewSliderEvents():
event, set_event = idom.hooks.use_state(None)

return idom.html.div(
MaterialButton(
material_ui.Button(
{
"color": "primary",
"variant": "contained",
Expand Down
5 changes: 2 additions & 3 deletions docs/source/examples/material_ui_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
import idom


material_ui = idom.install("@material-ui/core")
MaterialSlider = material_ui.define("Slider", fallback="loading...")
material_ui = idom.install("@material-ui/core", fallback="loading...")


@idom.element
def ViewSliderEvents():
event, set_event = idom.hooks.use_state(None)

return idom.html.div(
MaterialSlider(
material_ui.Slider(
{
"color": "primary",
"step": 10,
Expand Down
4 changes: 2 additions & 2 deletions docs/source/examples/simple_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from idom.widgets.html import Input


VictoryLine = idom.install("victory").define("VictoryLine", fallback="loading...")
victory = idom.install("victory", fallback="loading...")


@idom.element
Expand Down Expand Up @@ -53,7 +53,7 @@ async def animate():
}
set_data(data[1:] + [next_data_point])

return VictoryLine({"data": data, "style": {"parent": {"width": "500px"}}})
return victory.VictoryLine({"data": data, "style": {"parent": {"width": "500px"}}})


@idom.element
Expand Down
5 changes: 2 additions & 3 deletions docs/source/examples/super_simple_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@


path_to_source_file = Path(__file__).parent / "super_simple_chart.js"
super_simple_chart = idom.Module("super-simple-chart", source_file=path_to_source_file)
SuperSimpleChart = super_simple_chart.define("SuperSimpleChart")
ssc = idom.Module("super-simple-chart", source_file=path_to_source_file)


idom.run(
idom.element(
lambda: SuperSimpleChart(
lambda: ssc.SuperSimpleChart(
{
"data": [
{"x": 1, "y": 2},
Expand Down
5 changes: 2 additions & 3 deletions docs/source/examples/victory_chart.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import idom

victory = idom.install("victory")
VictoryBar = victory.define("VictoryBar", fallback="loading...")
victory = idom.install("victory", fallback="loading...")

idom.run(
idom.element(
lambda: VictoryBar({"style": {"parent": {"width": "500px"}}}),
lambda: victory.VictoryBar({"style": {"parent": {"width": "500px"}}}),
)
)
2 changes: 1 addition & 1 deletion idom/client/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 31 additions & 13 deletions idom/client/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,27 @@


@overload
def install(packages: str) -> "Module":
def install(
packages: str,
ignore_installed: bool,
fallback: Optional[str],
) -> "Module":
...


@overload
def install(packages: Union[List[str], Tuple[str]]) -> List["Module"]:
def install(
packages: Union[List[str], Tuple[str]],
ignore_installed: bool,
fallback: Optional[str],
) -> List["Module"]:
...


def install(
packages: Union[str, List[str], Tuple[str]], ignore_installed: bool = False
packages: Union[str, List[str], Tuple[str]],
ignore_installed: bool = False,
fallback: Optional[str] = None,
) -> Union["Module", List["Module"]]:
return_one = False
if isinstance(packages, str):
Expand All @@ -38,7 +48,11 @@ def install(
f"{client.current} failed to discover {list(not_discovered)}."
)

return Module(pkg_names.pop()) if return_one else [Module(pkg) for pkg in pkg_names]
return (
Module(pkg_names.pop(), fallback=fallback)
if return_one
else [Module(pkg, fallback=fallback) for pkg in pkg_names]
)


class Module:
Expand All @@ -63,7 +77,7 @@ class Module:
The URL this module will be imported from.
"""

__slots__ = "url", "exports", "fallback"
__slots__ = "url", "fallback", "exports", "_export_names"

def __init__(
self,
Expand All @@ -73,25 +87,26 @@ def __init__(
check_exports: bool = True,
) -> None:
self.fallback = fallback
self.exports: Optional[List[str]] = None
self._export_names: Optional[List[str]] = None
if source_file is not None:
self.url = (
client.current.web_module_url(url_or_name)
if client.current.web_module_exists(url_or_name)
else client.current.add_web_module(url_or_name, source_file)
)
if check_exports:
self.exports = client.current.web_module_exports(url_or_name)
self._export_names = client.current.web_module_exports(url_or_name)
elif client.current.web_module_exists(url_or_name):
self.url = client.current.web_module_url(url_or_name)
if check_exports:
self.exports = client.current.web_module_exports(url_or_name)
self._export_names = client.current.web_module_exports(url_or_name)
elif _is_url(url_or_name):
self.url = url_or_name
else:
raise ValueError(f"{url_or_name!r} is not installed or is not a URL")
self.exports = {name: self.declare(name) for name in (self._export_names or [])}

def define(
def declare(
self,
name: str,
has_children: bool = True,
Expand All @@ -109,16 +124,19 @@ def define(
this :class:`Module` instance.
"""
if (
self.exports is not None
self._export_names is not None
# if 'default' is exported there's not much we can infer
and "default" not in self.exports
and "default" not in self._export_names
):
if name not in self.exports:
if name not in self._export_names:
raise ValueError(
f"{self} does not export {name!r}, available options are {self.exports}"
f"{self} does not export {name!r}, available options are {self._export_names}"
)
return Import(self.url, name, has_children, fallback=fallback or self.fallback)

def __getattr__(self, name: str) -> "Import":
return self.exports.get(name) or self.declare(name)

def __repr__(self) -> str:
return f"{type(self).__name__}({self.url})"

Expand Down
14 changes: 5 additions & 9 deletions tests/test_client/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_any_relative_or_abolute_url_allowed():

def test_module_import_repr():
assert (
repr(Module("/absolute/url/module").define("SomeComponent"))
repr(Module("/absolute/url/module").declare("SomeComponent"))
== "Import(name='SomeComponent', source='/absolute/url/module', fallback=None)"
)

Expand All @@ -27,7 +27,7 @@ def test_module_does_not_exist():


def test_installed_module(driver, display, victory):
display(victory.define("VictoryBar"))
display(victory.VictoryBar)
driver.find_element_by_class_name("VictoryContainer")


Expand Down Expand Up @@ -79,25 +79,21 @@ def add_web_module(self, package_name, source):

fake = Module("fake-name")
assert fake.url == "./mock/url/to/module-fake-name.js"
assert fake.exports == ["x", "y", "z"]
assert list(fake.exports) == ["x", "y", "z"]
assert fake.fallback is None

with pytest.raises(ValueError, match="does not export 'DoesNotExist'"):
fake.define("DoesNotExist")

for name in fake.exports:
fake.define(name)
fake.declare("DoesNotExist")


def test_module_from_source(driver, driver_wait, display):
test_module = Module("test-module", source_file=HERE / "test_js_module.js")
test_button = test_module.define("TestButton")

response_data = idom.Ref(None)

@idom.element
def ShowButton():
return test_button(
return test_module.TestButton(
{
"id": "test-button",
"onClick": lambda event: response_data.set_current(event["data"]),
Expand Down

0 comments on commit 9aa5183

Please sign in to comment.