From 68a4c6510d327c200ff117ce3cc269eaf69e7479 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Apr 2023 19:42:03 +0200 Subject: [PATCH 1/7] Add support for serving Markdown files --- doc/how_to/display/examples/hello_world.md | 18 +++++ doc/how_to/display/index.md | 7 ++ doc/how_to/display/markdown.md | 67 ++++++++++++++++ panel/io/convert.py | 2 +- panel/io/jupyter_executor.py | 2 +- panel/io/markdown.py | 93 ++++++++++++++++++++++ panel/io/server.py | 6 +- panel/template/__init__.py | 2 +- 8 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 doc/how_to/display/examples/hello_world.md create mode 100644 doc/how_to/display/markdown.md create mode 100644 panel/io/markdown.py diff --git a/doc/how_to/display/examples/hello_world.md b/doc/how_to/display/examples/hello_world.md new file mode 100644 index 0000000000..71a29aa1f4 --- /dev/null +++ b/doc/how_to/display/examples/hello_world.md @@ -0,0 +1,18 @@ +# My App + +```panel +import panel as pn + +pn.extension(template='fast') +``` + +This application provides a minimal example demonstrating how to write an app in a Markdown file. + +```panel +widget = pn.widgets.TextInput(value='world') + +def hello_world(text): + return f'Hello {text}!' + +pn.Row(widget, pn.bind(hello_world, widget)).servable() +``` diff --git a/doc/how_to/display/index.md b/doc/how_to/display/index.md index 6b575c74ea..ff0249f3a8 100644 --- a/doc/how_to/display/index.md +++ b/doc/how_to/display/index.md @@ -28,6 +28,13 @@ How to rapidly develop a Panel application in your favorite IDE or editor. How to use the Preview functionality in JupyterLab to rapidly develop applications. ::: +:::{grid-item-card} {octicon}`markdown;2.5em;sd-mr-1 sd-animate-grow50` Write apps in Markdown +:link: markdown +:link-type: doc + +How to write Panel applications inside Markdown files. +::: + :::: ```{toctree} diff --git a/doc/how_to/display/markdown.md b/doc/how_to/display/markdown.md new file mode 100644 index 0000000000..c722ef155c --- /dev/null +++ b/doc/how_to/display/markdown.md @@ -0,0 +1,67 @@ +# Write apps in Markdown + +This guide addresses how to write Panel apps inside Markdown files. + +--- + +Panel applications can be written as Python scripts (`.py`), notebooks (`.ipynb`) and also Markdown files (`.md`). This is particularly useful when writing applications that serve both as documentation and as an application, e.g. when writing a demo. + +To begin simply create a Markdown file with the `.md` file extension, e.g. `app.md`. Once created give your app a title: + +```markdown +# My App +``` + +Before adding any actual content add a code block with any imports your application needs. The code block should have one of two type declarations, either `panel` or `{pyodide}`. The latter is useful if you also want to use [the Sphinx Pyodide integration](../wasm/sphinx.md). In this case we will simply declare a code block that imports Panel and calls the extension with a specific template: + +````markdown +```panel +import panel as pn + +pn.extension(template='fast') +``` +```` + +Once we have initialized the extension any subsequent Markdown blocks will be rendered as part of the application, e.g. we can put some description in our application. + +```markdown +This application provides a minimal example demonstrating how to write an app in a Markdown file. +``` + +Now we can add some actual Panel contents, again inside a Panel code block: + +````markdown +```panel +widget = pn.widgets.TextInput(value='world') + +def hello_world(text): + return f'Hello {text}!' + +pn.Row(widget, hello_world).servable() +``` +```` + +To put it all together, here is what our app looks like: + +````markdown +# My App + +```panel +import panel as pn + +pn.extension(template='fast') +``` + +This application provides a minimal example demonstrating how to write an app in a Markdown file. + +```panel +widget = pn.widgets.TextInput(value='world') + +def hello_world(text): + return f'Hello {text}!' + +pn.Row(widget, hello_world).servable() +``` +```` + +![The rendered Panel application written as a Markdown file.](../../_static/markdown_sample.png) diff --git a/panel/io/convert.py b/panel/io/convert.py index c75dbbef50..63254acaeb 100644 --- a/panel/io/convert.py +++ b/panel/io/convert.py @@ -14,7 +14,6 @@ from bokeh.application.application import Application, SessionContext from bokeh.application.handlers.code import CodeHandler -from bokeh.command.util import build_single_handler_application from bokeh.core.json_encoder import serialize_json from bokeh.core.templates import MACROS, get_env from bokeh.document import Document @@ -26,6 +25,7 @@ from .. import __version__, config from ..util import base_version, escape +from .markdown import build_single_handler_application from .mime_render import find_imports from .resources import ( BASE_TEMPLATE, CDN_DIST, DIST_DIR, INDEX_TEMPLATE, Resources, diff --git a/panel/io/jupyter_executor.py b/panel/io/jupyter_executor.py index 3a0506060a..6581ea21c6 100644 --- a/panel/io/jupyter_executor.py +++ b/panel/io/jupyter_executor.py @@ -10,7 +10,6 @@ import tornado -from bokeh.command.util import build_single_handler_application from bokeh.document import Document from bokeh.embed.bundle import extension_dirs from bokeh.protocol import Protocol @@ -25,6 +24,7 @@ from ipykernel.comm import Comm from ..util import edit_readonly +from .markdown import build_single_handler_application from .resources import Resources from .server import server_html_page_for_session from .state import set_curdoc, state diff --git a/panel/io/markdown.py b/panel/io/markdown.py new file mode 100644 index 0000000000..738c0d06a5 --- /dev/null +++ b/panel/io/markdown.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import os + +from typing import IO + +import bokeh.command.util + +from bokeh.application.handlers.code import CodeHandler +from bokeh.command.util import ( + build_single_handler_applications as _build_application, +) + + +def extract_code( + filehandle: IO, supported_syntax: tuple[str, ...] = ('{pyodide}', 'panel') +) -> str: + """ + Extracts Panel application code from a Markdown file. + """ + inblock = False + title = None + markdown = [] + out = [] + while True: + line = filehandle.readline() + if not line: + # EOF + break + + if line.strip() == "": + continue + + lsline = line.lstrip() + if lsline.startswith("```"): + if inblock: + inblock = False + continue + num_leading_backticks = len(lsline) - len(lsline.lstrip("`")) + syntax = line.strip()[num_leading_backticks:] + if syntax in supported_syntax: + if markdown: + md = '\n'.join(markdown) + markdown.clear() + if any('pn.extension' in o for o in out): + out.append(f"pn.pane.Markdown({md!r}).servable()\n") + inblock = True + else: + markdown.append(line) + elif inblock: + out.append(line) + elif line.startswith('# '): + title = line[1:].lstrip() + else: + markdown.append(line) + if title and any('template=' in o for o in out if 'pn.extension' in o): + out.append(f'pn.state.template.title = {title.strip()!r}') + return '\n'.join(out) + +class MarkdownHandler(CodeHandler): + ''' Modify Bokeh documents by creating Dashboard from a Markdown file. + + ''' + + def __init__(self, *args, **kwargs): + ''' + + Keywords: + filename (str) : a path to a Markdown (".md") file + + ''' + if 'filename' not in kwargs: + raise ValueError('Must pass a filename to Handler') + filename = os.path.abspath(kwargs['filename']) + with open(filename, encoding='utf-8') as f: + code = extract_code(f) + kwargs['source'] = code + super().__init__(*args, **kwargs) + +def build_single_handler_application(path, argv): + if not os.path.isfile(path) or not path.endswith(".md"): + return _build_application(path, argv) + + from .server import Application + handler = MarkdownHandler(filename=path) + if handler.failed: + raise RuntimeError("Error loading %s:\n\n%s\n%s " % (path, handler.error, handler.error_detail)) + + application = Application(handler) + + return application + +bokeh.command.util.build_single_handler_application = build_single_handler_application diff --git a/panel/io/server.py b/panel/io/server.py index 48d06b8b21..186213e9eb 100644 --- a/panel/io/server.py +++ b/panel/io/server.py @@ -38,7 +38,6 @@ CodeHandler, _monkeypatch_io, patch_curdoc, ) from bokeh.application.handlers.function import FunctionHandler -from bokeh.command.util import build_single_handler_application from bokeh.core.templates import AUTOLOAD_JS from bokeh.core.validation import silence from bokeh.core.validation.warnings import EMPTY_LAYOUT @@ -70,6 +69,7 @@ from .logging import ( LOG_SESSION_CREATED, LOG_SESSION_DESTROYED, LOG_SESSION_LAUNCHING, ) +from .markdown import build_single_handler_application from .profile import profile_ctx from .reload import autoreload_watcher from .resources import ( @@ -884,7 +884,7 @@ def get_server( continue if isinstance(app, pathlib.Path): app = str(app) # enables serving apps from Paths - if (isinstance(app, str) and (app.endswith(".py") or app.endswith(".ipynb")) + if (isinstance(app, str) and (app.endswith(".py") or app.endswith(".ipynb") or app.endswith('.md')) and os.path.isfile(app)): apps[slug] = app = build_single_handler_application(app) app._admin = admin @@ -896,7 +896,7 @@ def get_server( else: if isinstance(panel, pathlib.Path): panel = str(panel) # enables serving apps from Paths - if (isinstance(panel, str) and (panel.endswith(".py") or panel.endswith(".ipynb")) + if (isinstance(panel, str) and (panel.endswith(".py") or panel.endswith(".ipynb") or panel.endswith('.md')) and os.path.isfile(panel)): apps = {'/': build_single_handler_application(panel)} else: diff --git a/panel/template/__init__.py b/panel/template/__init__.py index 2c0d2f3645..4e3920ea65 100644 --- a/panel/template/__init__.py +++ b/panel/template/__init__.py @@ -17,8 +17,8 @@ 'vanilla' : VanillaTemplate } -_config.param.template.names = templates _config.param.template.objects = list(templates) +_config.param.template.names = templates __all__ = [ "BaseTemplate", From 6d14678a591ba0c98091797d900a553ceff73dbd Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Apr 2023 19:58:28 +0200 Subject: [PATCH 2/7] Add test --- panel/tests/command/test_serve.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/panel/tests/command/test_serve.py b/panel/tests/command/test_serve.py index 7e43f5a90d..8203015083 100644 --- a/panel/tests/command/test_serve.py +++ b/panel/tests/command/test_serve.py @@ -58,6 +58,7 @@ def _populateQueue(stream, queue): while True: line = stream.readline() + print(line) if line: queue.put(line) else: @@ -140,3 +141,29 @@ def test_custom_html_index(relative, html_file): r = requests.get(f"http://localhost:{port}/") assert r.status_code == 200 assert r.content.decode('utf-8') == index + +md_app = """ +# My app + +```panel +import panel as pn +pn.extension(template='fast') +``` + +A description + +```panel +pn.Row('# Example').servable() +``` +""" + +@not_windows +def test_serve_markdown(): + md = tempfile.NamedTemporaryFile(mode='w', suffix='.md') + write_file(md_app, md.file) + + with run_panel_serve(["--port", "0", md.name]) as p: + port = wait_for_port(p.stdout) + r = requests.get(f"http://localhost:{port}/") + assert r.status_code == 200 + assert 'My app' in r.content.decode('utf-8') From 883cd74529d1455300c3a50a485fae97925fb39f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Apr 2023 20:12:04 +0200 Subject: [PATCH 3/7] Add unit tests --- panel/io/markdown.py | 4 +++ panel/tests/io/test_markdown.py | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 panel/tests/io/test_markdown.py diff --git a/panel/io/markdown.py b/panel/io/markdown.py index 738c0d06a5..30ef433591 100644 --- a/panel/io/markdown.py +++ b/panel/io/markdown.py @@ -53,6 +53,10 @@ def extract_code( title = line[1:].lstrip() else: markdown.append(line) + if markdown: + md = '\n'.join(markdown) + if any('pn.extension' in o for o in out): + out.append(f"pn.pane.Markdown({md!r}).servable()\n") if title and any('template=' in o for o in out if 'pn.extension' in o): out.append(f'pn.state.template.title = {title.strip()!r}') return '\n'.join(out) diff --git a/panel/tests/io/test_markdown.py b/panel/tests/io/test_markdown.py new file mode 100644 index 0000000000..4bd3dcc68d --- /dev/null +++ b/panel/tests/io/test_markdown.py @@ -0,0 +1,59 @@ +from io import StringIO + +from panel.io.markdown import extract_code + +md1 = """ +```panel +import panel as pn + +pn.Row(1, 2, 3).servable() +``` +""" + +md2 = """ +```{pyodide} +import panel as pn + +pn.Row(1, 2, 3).servable() +``` +""" + +md3 = """ +```panel +import panel as pn +pn.extension() +``` + +My description + +```panel +pn.Row(1, 2, 3).servable() +``` +""" + +md4 = """ +# My app + +```panel +import panel as pn +pn.extension(template='fast') + +pn.Row(1, 2, 3).servable() +``` +""" + +def test_extract_panel_block(): + f = StringIO(md1) + assert extract_code(f) == "import panel as pn\n\npn.Row(1, 2, 3).servable()\n" + +def test_extract_pyodide_block(): + f = StringIO(md2) + assert extract_code(f) == "import panel as pn\n\npn.Row(1, 2, 3).servable()\n" + +def test_extract_description_block(): + f = StringIO(md3) + assert extract_code(f) == "import panel as pn\n\npn.extension()\n\npn.pane.Markdown('My description\\n').servable()\n\npn.Row(1, 2, 3).servable()\n" + +def test_extract_title_block(): + f = StringIO(md4) + assert extract_code(f) == "import panel as pn\n\npn.extension(template='fast')\n\npn.Row(1, 2, 3).servable()\n\npn.state.template.title = 'My app'" From d9590a884223586a3881e2c7c5792ed319b9b3e6 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Apr 2023 20:13:30 +0200 Subject: [PATCH 4/7] Add image --- doc/_static/markdown_sample.png | Bin 0 -> 13454 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/_static/markdown_sample.png diff --git a/doc/_static/markdown_sample.png b/doc/_static/markdown_sample.png new file mode 100644 index 0000000000000000000000000000000000000000..9ce72c3cbe55047942f4796b37e9e11641baba26 GIT binary patch literal 13454 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 literal 0 HcmV?d00001 From 5593ff79585ea4fc1ef6e29d1611fe677e950c5b Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Apr 2023 20:23:22 +0200 Subject: [PATCH 5/7] Fix tests --- panel/io/markdown.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panel/io/markdown.py b/panel/io/markdown.py index 30ef433591..ca225cecb5 100644 --- a/panel/io/markdown.py +++ b/panel/io/markdown.py @@ -8,7 +8,7 @@ from bokeh.application.handlers.code import CodeHandler from bokeh.command.util import ( - build_single_handler_applications as _build_application, + build_single_handler_application as _build_application, ) @@ -81,7 +81,7 @@ def __init__(self, *args, **kwargs): kwargs['source'] = code super().__init__(*args, **kwargs) -def build_single_handler_application(path, argv): +def build_single_handler_application(path, argv=None): if not os.path.isfile(path) or not path.endswith(".md"): return _build_application(path, argv) From 4e25cb87c06c5916ab31fb1b1c4b1b724aa33455 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Apr 2023 20:38:09 +0200 Subject: [PATCH 6/7] Support python file extension --- doc/_static/markdown_sample.png | Bin 13454 -> 27126 bytes doc/how_to/display/examples/hello_world.md | 13 +++++++-- doc/how_to/display/markdown.md | 32 ++++++++++++++++----- panel/io/markdown.py | 2 +- panel/tests/command/test_serve.py | 4 +-- panel/tests/io/test_markdown.py | 8 +++--- 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/doc/_static/markdown_sample.png b/doc/_static/markdown_sample.png index 9ce72c3cbe55047942f4796b37e9e11641baba26..aa414e3fa5732f7eb34c4f383d208697a22f4ea9 100644 GIT binary patch literal 27126 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 literal 13454 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 diff --git a/doc/how_to/display/examples/hello_world.md b/doc/how_to/display/examples/hello_world.md index 71a29aa1f4..3f505beb2e 100644 --- a/doc/how_to/display/examples/hello_world.md +++ b/doc/how_to/display/examples/hello_world.md @@ -1,6 +1,6 @@ # My App -```panel +```python import panel as pn pn.extension(template='fast') @@ -8,7 +8,16 @@ pn.extension(template='fast') This application provides a minimal example demonstrating how to write an app in a Markdown file. -```panel +```.py +widget = pn.widgets.TextInput(value='world') + +def hello_world(text): + return f'Hello {text}!' + +pn.Row(widget, pn.bind(hello_world, widget)).servable() +``` + +```python widget = pn.widgets.TextInput(value='world') def hello_world(text): diff --git a/doc/how_to/display/markdown.md b/doc/how_to/display/markdown.md index c722ef155c..b23b0eb6c9 100644 --- a/doc/how_to/display/markdown.md +++ b/doc/how_to/display/markdown.md @@ -12,26 +12,35 @@ To begin simply create a Markdown file with the `.md` file extension, e.g. `app. # My App ``` -Before adding any actual content add a code block with any imports your application needs. The code block should have one of two type declarations, either `panel` or `{pyodide}`. The latter is useful if you also want to use [the Sphinx Pyodide integration](../wasm/sphinx.md). In this case we will simply declare a code block that imports Panel and calls the extension with a specific template: +Before adding any actual content add a code block with any imports your application needs. The code block should have one of two type declarations, either `python` or `{pyodide}`. The latter is useful if you also want to use [the Sphinx Pyodide integration](../wasm/sphinx.md). In this case we will simply declare a code block that imports Panel and calls the extension with a specific template: ````markdown -```panel +```python import panel as pn pn.extension(template='fast') ``` ```` -Once we have initialized the extension any subsequent Markdown blocks will be rendered as part of the application, e.g. we can put some description in our application. +Once we have initialized the extension any subsequent Markdown blocks will be rendered as part of the application, e.g. we can put some description in our application. If you also want to render some Python code with having Panel interpret it as code use `.py` as the language declaration: -```markdown +````markdown This application provides a minimal example demonstrating how to write an app in a Markdown file. + +```.py +widget = pn.widgets.TextInput(value='world') + +def hello_world(text): + return f'Hello {text}!' + +pn.Row(widget, pn.bind(hello_world, widget)).servable() ``` +```` Now we can add some actual Panel contents, again inside a Panel code block: ````markdown -```panel +```python widget = pn.widgets.TextInput(value='world') def hello_world(text): @@ -46,7 +55,7 @@ To put it all together, here is what our app looks like: ````markdown # My App -```panel +```python import panel as pn pn.extension(template='fast') @@ -54,7 +63,16 @@ pn.extension(template='fast') This application provides a minimal example demonstrating how to write an app in a Markdown file. -```panel +```.py +widget = pn.widgets.TextInput(value='world') + +def hello_world(text): + return f'Hello {text}!' + +pn.Row(widget, pn.bind(hello_world, widget)).servable() +``` + +```python widget = pn.widgets.TextInput(value='world') def hello_world(text): diff --git a/panel/io/markdown.py b/panel/io/markdown.py index ca225cecb5..71b8d4cd40 100644 --- a/panel/io/markdown.py +++ b/panel/io/markdown.py @@ -13,7 +13,7 @@ def extract_code( - filehandle: IO, supported_syntax: tuple[str, ...] = ('{pyodide}', 'panel') + filehandle: IO, supported_syntax: tuple[str, ...] = ('{pyodide}', 'python') ) -> str: """ Extracts Panel application code from a Markdown file. diff --git a/panel/tests/command/test_serve.py b/panel/tests/command/test_serve.py index 8203015083..07fd3094fb 100644 --- a/panel/tests/command/test_serve.py +++ b/panel/tests/command/test_serve.py @@ -145,14 +145,14 @@ def test_custom_html_index(relative, html_file): md_app = """ # My app -```panel +```python import panel as pn pn.extension(template='fast') ``` A description -```panel +```python pn.Row('# Example').servable() ``` """ diff --git a/panel/tests/io/test_markdown.py b/panel/tests/io/test_markdown.py index 4bd3dcc68d..8743f33070 100644 --- a/panel/tests/io/test_markdown.py +++ b/panel/tests/io/test_markdown.py @@ -3,7 +3,7 @@ from panel.io.markdown import extract_code md1 = """ -```panel +```python import panel as pn pn.Row(1, 2, 3).servable() @@ -19,14 +19,14 @@ """ md3 = """ -```panel +```python import panel as pn pn.extension() ``` My description -```panel +```python pn.Row(1, 2, 3).servable() ``` """ @@ -34,7 +34,7 @@ md4 = """ # My app -```panel +```python import panel as pn pn.extension(template='fast') From fbd53fbe594c96c2575ed1cfe4f62d0b43d96f14 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Apr 2023 20:41:15 +0200 Subject: [PATCH 7/7] Fix docs --- doc/how_to/display/markdown.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/how_to/display/markdown.md b/doc/how_to/display/markdown.md index b23b0eb6c9..e49b5788ea 100644 --- a/doc/how_to/display/markdown.md +++ b/doc/how_to/display/markdown.md @@ -12,7 +12,7 @@ To begin simply create a Markdown file with the `.md` file extension, e.g. `app. # My App ``` -Before adding any actual content add a code block with any imports your application needs. The code block should have one of two type declarations, either `python` or `{pyodide}`. The latter is useful if you also want to use [the Sphinx Pyodide integration](../wasm/sphinx.md). In this case we will simply declare a code block that imports Panel and calls the extension with a specific template: +Before adding any actual content add a code block with any imports your application needs. The code block should have one of two type declarations, either `python` or `{pyodide}`. The latter is useful if you also want to use [the Sphinx Pyodide integration](../wasm/sphinx.md). In this case we will simply declare a `python` code block that imports Panel and calls the extension with a specific template: ````markdown ```python @@ -22,7 +22,7 @@ pn.extension(template='fast') ``` ```` -Once we have initialized the extension any subsequent Markdown blocks will be rendered as part of the application, e.g. we can put some description in our application. If you also want to render some Python code with having Panel interpret it as code use `.py` as the language declaration: +Once we have initialized the extension any subsequent Markdown will be rendered as part of the application, e.g. we can put some description in our application. If you also want to render some Python code without having Panel interpret it as code, use `.py` as the language declaration: ````markdown This application provides a minimal example demonstrating how to write an app in a Markdown file. @@ -37,7 +37,7 @@ pn.Row(widget, pn.bind(hello_world, widget)).servable() ``` ```` -Now we can add some actual Panel contents, again inside a Panel code block: +Now we can add some actual Panel contents, again inside a `python` code block: ````markdown ```python