Skip to content

Commit 3187052

Browse files
authored
Merge pull request #605 from facultyai/julia-snippets
Julia snippets
2 parents 64bebaf + ba90d06 commit 3187052

File tree

216 files changed

+2961
-177
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

216 files changed

+2961
-177
lines changed

.github/workflows/doctest.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,19 @@ jobs:
4343
sudo apt-get install r-recommended r-cran-dash
4444
- name: Install R package
4545
run: sudo R CMD INSTALL .
46-
- name: Test installation
47-
run: library(dashBootstrapComponents)
48-
shell: Rscript {0}
46+
- name: Setup Julia
47+
uses: julia-actions/setup-julia@v1
48+
with:
49+
version: '1.6.1'
50+
- name: Add Julia artifacts to repo for local install
51+
run: |
52+
git add -f deps src/*.jl Project.toml
53+
git -c user.name="GitHub Actions" -c user.email="actions@github.com" commit -m "Add Julia build artifacts"
54+
- name: Install Julia dependencies
55+
run: julia -e 'using Pkg; Pkg.add(["Dash", "DashCoreComponents", "DashHtmlComponents", "HTTP"]); Pkg.add(path=".");'
56+
- name: Test Julia installation
57+
run: julia -e 'using Dash, DashBootstrapComponents, HTTP;'
4958
- name: Install nox
5059
run: python3.8 -m pip install -U nox
51-
- name: Test R docs snippets
60+
- name: Test R and Julia docs snippets
5261
run: nox -s doctest

docs/components_page/components/__tests__/helpers.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ def py_source_to_app(py_source, env):
55
"""
66
Create a Dash app from a string defining the app.
77
"""
8+
env = env or {}
89
exec(py_source, env)
910
return env["app"]
1011

1112

1213
def drop_keys(d):
1314
"""
14-
Layout payload for R apps has a slightly different structure. We drop some
15-
keys for parity.
15+
Layout payload for R and Julia apps has a slightly different structure. We
16+
drop some keys for parity.
1617
"""
1718
if isinstance(d, dict):
1819
return {
@@ -31,3 +32,18 @@ def clean_path(path):
3132
if path.startswith("components/"):
3233
path = path[11:]
3334
return Path(path)
35+
36+
37+
def rename_variable(snippet_path, suffix, variable, assign_op="="):
38+
with snippet_path.open() as f:
39+
lines = f.read().split("\n")
40+
41+
new_lines = []
42+
for line in lines:
43+
if line.startswith(f"{variable} {assign_op}"):
44+
line = line.replace(
45+
f"{variable} {assign_op}", f"{variable}_{suffix} {assign_op}"
46+
)
47+
new_lines.append(line)
48+
49+
return "\n".join(new_lines)

docs/components_page/components/__tests__/test_snippets.py

Lines changed: 117 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,142 @@
33
"""
44
import re
55
import unittest
6-
from collections import defaultdict
76
from pathlib import Path
87

98
import pytest
109
import requests
11-
from dash.testing.composite import DashRComposite
10+
from dash.testing.application_runners import JuliaRunner, RRunner
1211

13-
from .helpers import clean_path, drop_keys, py_source_to_app
14-
from .wrappers import PY_WRAPPER, R_WRAPPER
12+
from .helpers import clean_path, drop_keys, py_source_to_app, rename_variable
13+
from .wrappers import JL_WRAPPER, PY_WRAPPER, R_WRAPPER
1514

1615
HERE = Path(__file__).parent
1716

1817
PATTERN = re.compile(r"{{example:(.*)}}")
1918
PARAMS = [
20-
match.split(":")
19+
(
20+
path,
21+
[match.split(":") for match in PATTERN.findall(path.read_text())],
22+
)
2123
for path in HERE.parent.glob("*.md")
22-
for match in PATTERN.findall(path.read_text())
2324
]
2425

2526
SKIP = ["components/table/kwargs.py", "components/tabs/active_tab.py"]
26-
ENVS = defaultdict(
27-
dict,
28-
{
29-
"components/modal/scrollable.py": {
30-
"LOREM": (HERE.parent / "modal" / "lorem.txt").read_text().strip()
31-
}
32-
},
33-
)
27+
ENVS = {
28+
"modal.md": {
29+
"LOREM": (HERE.parent / "modal" / "lorem.txt").read_text().strip()
30+
}
31+
}
32+
33+
R_PORT = 8051
34+
JL_PORT = 8053
3435

3536

3637
@pytest.fixture
37-
def dashr(request, dashr_server, tmpdir):
38-
with DashRComposite(
39-
dashr_server,
40-
browser=request.config.getoption("webdriver"),
41-
remote=request.config.getoption("remote"),
42-
remote_url=request.config.getoption("remote_url"),
43-
headless=request.config.getoption("headless"),
44-
options=request.config.hook.pytest_setup_options(),
45-
download_path=tmpdir.mkdir("download-r").strpath,
46-
percy_assets_root=request.config.getoption("percy_assets"),
47-
percy_finalize=request.config.getoption("nopercyfinalize"),
48-
pause=request.config.getoption("pause"),
49-
) as dc:
50-
yield dc
38+
def dashr_server():
39+
with RRunner() as starter:
40+
starter.port = R_PORT
41+
yield starter
42+
43+
44+
@pytest.fixture
45+
def dashjl_server():
46+
with JuliaRunner() as starter:
47+
starter.port = JL_PORT
48+
yield starter
5149

5250

5351
@pytest.mark.parametrize("config", PARAMS)
54-
def test_snippets(dash_duo, dashr, config):
55-
path, name = config
56-
57-
if path not in SKIP:
58-
env = ENVS[path]
59-
path = HERE.parent / clean_path(path)
60-
r_snippet = path.parent / f"{path.stem}.R"
61-
if r_snippet.exists():
62-
# Get python snippet layout
63-
app = py_source_to_app(
64-
PY_WRAPPER.format(snippet=path.read_text(), name=name), env=env
65-
)
66-
dash_duo.start_server(app, port=8051)
67-
py_layout = requests.get(
68-
f"{dash_duo.server_url}/_dash-layout"
69-
).json()
70-
71-
# Get R snippet layout
72-
dashr.start_server(
73-
R_WRAPPER.format(snippet=r_snippet.read_text(), name=name)
74-
)
75-
r_layout = requests.get(f"{dashr.server_url}/_dash-layout").json()
52+
def test_r_snippets(dash_thread_server, dashr_server, config):
53+
md_path, data = config
54+
env = ENVS.get(md_path.name)
7655

77-
# Test layouts match
78-
unittest.TestCase().assertDictEqual(
79-
drop_keys(py_layout), drop_keys(r_layout)
56+
python_r_compare = []
57+
58+
# Concatenate all the snippets in the markdown file together
59+
for i, (snippet_path, name) in enumerate(data):
60+
if snippet_path in SKIP:
61+
continue
62+
63+
snippet_path = HERE.parent / clean_path(snippet_path)
64+
py_snippet = rename_variable(snippet_path, i, name)
65+
66+
r_snippet_path = snippet_path.parent / f"{snippet_path.stem}.R"
67+
68+
if r_snippet_path.exists():
69+
r_snippet = rename_variable(
70+
r_snippet_path, i, name, assign_op="<-"
8071
)
72+
python_r_compare.append((py_snippet, r_snippet, f"{name}_{i}"))
73+
74+
assert_layouts_equal(
75+
python_r_compare,
76+
dashr_server,
77+
R_WRAPPER,
78+
R_PORT,
79+
dash_thread_server,
80+
env,
81+
8050,
82+
)
83+
84+
85+
@pytest.mark.parametrize("config", PARAMS)
86+
def test_jl_snippets(dash_thread_server, dashjl_server, config):
87+
md_path, data = config
88+
env = ENVS.get(md_path.name)
89+
90+
python_jl_compare = []
91+
92+
# Concatenate all the snippets in the markdown file together
93+
for i, (snippet_path, name) in enumerate(data):
94+
if snippet_path in SKIP:
95+
continue
96+
97+
snippet_path = HERE.parent / clean_path(snippet_path)
98+
py_snippet = rename_variable(snippet_path, i, name)
99+
100+
jl_snippet_path = snippet_path.parent / f"{snippet_path.stem}.jl"
101+
102+
if jl_snippet_path.exists():
103+
jl_snippet = rename_variable(jl_snippet_path, i, name)
104+
python_jl_compare.append((py_snippet, jl_snippet, f"{name}_{i}"))
105+
106+
assert_layouts_equal(
107+
python_jl_compare,
108+
dashjl_server,
109+
JL_WRAPPER,
110+
JL_PORT,
111+
dash_thread_server,
112+
env,
113+
8052,
114+
)
115+
116+
117+
def assert_layouts_equal(
118+
compare, runner, wrapper, port, py_runner, py_env, py_port
119+
):
120+
# Get python snippet layout
121+
app = py_source_to_app(
122+
PY_WRAPPER.format(
123+
snippet="\n".join(x[0] for x in compare),
124+
components=", ".join(x[2] for x in compare),
125+
),
126+
env=py_env,
127+
)
128+
py_runner.start(app, port=py_port)
129+
py_layout = requests.get(f"{py_runner.url}/_dash-layout").json()
130+
131+
# Get other language snippet layout
132+
runner.start(
133+
wrapper.format(
134+
snippet="\n".join(x[1] for x in compare),
135+
components=", ".join(x[2] for x in compare),
136+
port=port,
137+
)
138+
)
139+
layout = requests.get(f"{runner.url}/_dash-layout").json()
140+
141+
# Test layouts match
142+
unittest.TestCase().assertDictEqual(
143+
drop_keys(py_layout), drop_keys(layout)
144+
)

docs/components_page/components/__tests__/wrappers.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
{snippet}
88
9-
app.layout = {name}
9+
app.layout = html.Div([{components}])
1010
"""
1111

1212
R_WRAPPER = """
@@ -18,6 +18,17 @@
1818
1919
{snippet}
2020
21-
app$layout({name})
22-
app$run_server()
21+
app$layout(htmlDiv(list({components})))
22+
app$run_server(port = {port})
23+
"""
24+
25+
JL_WRAPPER = """
26+
using Dash, DashBootstrapComponents
27+
28+
app = dash(external_stylesheets=[dbc_themes.BOOTSTRAP]);
29+
30+
{snippet}
31+
32+
app.layout = html_div([{components}]);
33+
run_server(app, "127.0.0.1", {port});
2334
"""

docs/components_page/components/alert.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,30 @@ lead: Provide contextual feedback messages for user actions with the `Alert` com
77

88
Set the color of `Alert` using the `color` argument and one of the eight supported contextual color names.
99

10-
{{example:components/alert/simple.py:alerts}}
10+
{{example:components/alert/simple.py:simple_alerts}}
1111

1212
## Link color
1313

1414
The Bootstrap `alert-link` class can be used to color match links inside alerts to the color of the alert.
1515

16-
{{example:components/alert/link.py:alerts}}
16+
{{example:components/alert/link.py:linked_alerts}}
1717

1818
## Additional content
1919

2020
Alerts can contain additional HTML elements like headings, paragraphs and dividers.
2121

22-
{{example:components/alert/content.py:alert}}
22+
{{example:components/alert/content.py:complex_alert}}
2323

2424
## Dismissing
2525

2626
Set `dismissable=True` to add a dismiss button to the alert which closes the alert on click. You can also use the `is_open` property in callbacks to show or hide the alert. By default the alert appears and disappears with a fade animation. To disable this simply set `fade=False`.
2727

28-
{{example:components/alert/dismiss.py:alert}}
28+
{{example:components/alert/dismiss.py:dimissable_alert}}
2929

3030
## Automatic dismissal
3131

3232
You can have your `Alert` components dismiss themselves by using the `duration` keyword argument. Specify a duration in milliseconds after which you would like the `Alert` to dismiss itself when it first becomes visible.
3333

34-
{{example:components/alert/auto_dismiss.py:alert}}
34+
{{example:components/alert/auto_dismiss.py:auto_dismiss_alert}}
3535

3636
{{apidoc:src/components/Alert.js}}

docs/components_page/components/alert/auto_dismiss.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
library(dashBootstrapComponents)
22
library(dashHtmlComponents)
33

4-
alert <- htmlDiv(
4+
auto_dismiss_alert <- htmlDiv(
55
list(
66
dbcButton("Toggle", id = "alert-toggle-auto", className = "mr-1",
77
n_clicks = 0),
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using DashBootstrapComponents, DashHtmlComponents
2+
3+
auto_dismiss_alert = html_div([
4+
dbc_button("Toggle", id="alert-toggle-auto", className="mr-1", n_clicks=0),
5+
html_hr(),
6+
dbc_alert(
7+
"Hello! I am an auto-dismissing alert!",
8+
id="alert-auto",
9+
is_open=true,
10+
duration=4000,
11+
),
12+
]);
13+
14+
callback!(
15+
app,
16+
Output("alert-auto", "is_open"),
17+
Input("alert-toggle-auto", "n_clicks"),
18+
State("alert-auto", "is_open"),
19+
) do n, is_open
20+
return n > 0 ? is_open == 0 : is_open
21+
end;

docs/components_page/components/alert/auto_dismiss.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import dash_html_components as html
33
from dash.dependencies import Input, Output, State
44

5-
alert = html.Div(
5+
auto_dismiss_alert = html.Div(
66
[
77
dbc.Button(
88
"Toggle", id="alert-toggle-auto", className="mr-1", n_clicks=0

docs/components_page/components/alert/content.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
library(dashBootstrapComponents)
22
library(dashHtmlComponents)
33

4-
alert <- dbcAlert(
4+
complex_alert <- dbcAlert(
55
list(
66
htmlH4("Well done!", className = "alert-heading"),
77
htmlP(
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using DashBootstrapComponents, DashHtmlComponents
2+
3+
complex_alert = dbc_alert([
4+
html_h4("Well done!", className="alert-heading"),
5+
html_p(
6+
"This is a success alert with loads of extra text in it. So much " *
7+
"that you can see how spacing within an alert works with this " *
8+
"kind of content.",
9+
),
10+
html_hr(),
11+
html_p(
12+
"Let's put some more text down here, but remove the bottom margin",
13+
className="mb-0",
14+
),
15+
]);

0 commit comments

Comments
 (0)