-
-
Notifications
You must be signed in to change notification settings - Fork 531
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for writing applications in Markdown (#4602)
- Loading branch information
1 parent
8664950
commit 872c136
Showing
11 changed files
with
308 additions
and
6 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# My App | ||
|
||
```python | ||
import panel as pn | ||
|
||
pn.extension(template='fast') | ||
``` | ||
|
||
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() | ||
``` | ||
|
||
```python | ||
widget = pn.widgets.TextInput(value='world') | ||
|
||
def hello_world(text): | ||
return f'Hello {text}!' | ||
|
||
pn.Row(widget, pn.bind(hello_world, widget)).servable() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# 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 `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 | ||
import panel as pn | ||
|
||
pn.extension(template='fast') | ||
``` | ||
```` | ||
|
||
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. | ||
|
||
```.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 `python` code block: | ||
|
||
````markdown | ||
```python | ||
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 | ||
|
||
```python | ||
import panel as pn | ||
|
||
pn.extension(template='fast') | ||
``` | ||
|
||
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() | ||
``` | ||
|
||
```python | ||
widget = pn.widgets.TextInput(value='world') | ||
|
||
def hello_world(text): | ||
return f'Hello {text}!' | ||
|
||
pn.Row(widget, hello_world).servable() | ||
``` | ||
```` | ||
|
||
 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
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_application as _build_application, | ||
) | ||
|
||
|
||
def extract_code( | ||
filehandle: IO, supported_syntax: tuple[str, ...] = ('{pyodide}', 'python') | ||
) -> 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 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) | ||
|
||
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=None): | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from io import StringIO | ||
|
||
from panel.io.markdown import extract_code | ||
|
||
md1 = """ | ||
```python | ||
import panel as pn | ||
pn.Row(1, 2, 3).servable() | ||
``` | ||
""" | ||
|
||
md2 = """ | ||
```{pyodide} | ||
import panel as pn | ||
pn.Row(1, 2, 3).servable() | ||
``` | ||
""" | ||
|
||
md3 = """ | ||
```python | ||
import panel as pn | ||
pn.extension() | ||
``` | ||
My description | ||
```python | ||
pn.Row(1, 2, 3).servable() | ||
``` | ||
""" | ||
|
||
md4 = """ | ||
# My app | ||
```python | ||
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'" |