{"id":15288065,"url":"https://github.com/b4pt0r/streamlit_notebook","last_synced_at":"2026-01-02T02:54:50.560Z","repository":{"id":243668659,"uuid":"813078790","full_name":"B4PT0R/streamlit_notebook","owner":"B4PT0R","description":"A reactive notebook interface for Streamlit","archived":false,"fork":false,"pushed_at":"2025-02-01T19:33:48.000Z","size":480,"stargazers_count":22,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-21T23:18:27.500Z","etag":null,"topics":["notebook","reactive","streamlit"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/B4PT0R.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-10T12:53:17.000Z","updated_at":"2025-02-14T07:37:37.000Z","dependencies_parsed_at":"2024-06-10T14:52:51.880Z","dependency_job_id":"89c27fdf-3075-447d-9503-76c00f5d1e4a","html_url":"https://github.com/B4PT0R/streamlit_notebook","commit_stats":null,"previous_names":["b4pt0r/streamlit_notebook"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/B4PT0R%2Fstreamlit_notebook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/B4PT0R%2Fstreamlit_notebook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/B4PT0R%2Fstreamlit_notebook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/B4PT0R%2Fstreamlit_notebook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/B4PT0R","download_url":"https://codeload.github.com/B4PT0R/streamlit_notebook/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248674658,"owners_count":21143760,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["notebook","reactive","streamlit"],"created_at":"2024-09-30T15:44:00.865Z","updated_at":"2026-01-02T02:54:50.550Z","avatar_url":"https://github.com/B4PT0R.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Streamlit Notebook\n\nThe reactive notebook powered by Streamlit.\n\nStreamlit Notebook combines the interactive development experience of Jupyter notebooks with the reactivity and deployment simplicity of Streamlit apps. Write code in notebook-style cells with a professional-grade Python shell that maintains state across reruns, then deploy the exact same file as a production-ready Streamlit application.\n\nDesigned with AI integration in mind, it comes equipped with its own full-featured AI agent having full dynamic control over the notebook (requires an OpenAI API key). It can create, edit, run code cells, read documents, view images, support voice interaction, etc.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Demo](#demo)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n  - [Create a notebook](#create-a-notebook)\n  - [Run it](#run-it)\n  - [How it works?](#how-it-works)\n- [Notebook Interface - Core Concepts](#notebook-interface---core-concepts)\n  - [Cell Types](#cell-types)\n  - [Persistent Shell](#persistent-shell)\n  - [100% Standard Streamlit API](#100-standard-streamlit-api)\n- [Real-World Example](#real-world-example)\n- [Easy Deployment](#easy-deployment)\n- [Development Features](#development-features)\n  - [Two Modes](#two-modes)\n  - [File Management](#file-management)\n  - [Rich Content](#rich-content)\n  - [Advanced Display Control with display()](#advanced-display-control-with-display)\n- [Advanced Features](#advanced-features)\n  - [Streamlit Fragments](#streamlit-fragments)\n  - [Programmatic API](#programmatic-api)\n- [AI Agent Integration](#ai-agent-integration)\n  - [Installation](#installation-1)\n  - [Features](#features)\n  - [Quick Start](#quick-start-1)\n  - [Accessing the Agent Programmatically](#accessing-the-agent-programmatically)\n  - [Agent Capabilities](#agent-capabilities)\n  - [Configuration](#configuration)\n  - [Example Workflow](#example-workflow)\n  - [Best Practices](#best-practices)\n  - [Advanced: Custom Tools](#advanced-custom-tools)\n- [CLI Options](#cli-options)\n- [Deployment](#deployment)\n  - [Local Testing](#local-testing)\n  - [Streamlit Cloud](#streamlit-cloud)\n  - [Docker](#docker)\n  - [Production Best Practices](#production-best-practices)\n  - [Multi-Notebook Deployments](#multi-notebook-deployments)\n- [Use Cases](#use-cases)\n- [Contributing](#contributing)\n- [License](#license)\n- [Changelog](#changelog)\n\n## Overview\n\nWe all know Jupyter as the go-to for interactive programming, but it comes with its own limitations:\n- JSON format notebooks\n- converting notebooks to deployable apps isn't straightforward. \n- limited UI widget ecosystem, \n- limited UI/namespace reactivity\n- limited dynamic creation of widgets\n- limited programmatic creation and execution of cells\n- kernel / frontend dichotomy\n- Huge dependency tree\n\nStreamlit on the other hand is great for fast reactive apps development, with a huge components ecosystem (possibility to wrap any web component), and easy deployment on the cloud. But its \"rerun the whole script\" execution model (without namespace persistence!) can sometimes turn cumbersome, and it lacked a proper notebook ergonomics.\n\nStreamlit Notebook attempts to give you the best of both worlds.\n\n- **Development:** Cell-by-cell execution, full widget support, selective reactivity, persistent namespace, fast iteration, saved as readable and runnable `.py` files.\n- **Deployment:** Easily publish your notebook as a Streamlit app, no special runtime, deploy in couple of clicks, works anywhere Streamlit works.\n\n## Demo\n\nTry the hosted demo: [st-notebook.streamlit.app](https://st-notebook.streamlit.app/)\n\n## Installation\n\n**With everything** (recommended for most users):\nIncludes a complete datascience stack and a full featured AI agent.\n```bash\npip install streamlit-notebook[full]\n```\n\n**Basic installation** (core notebook functionality only):\n```bash\npip install streamlit-notebook\n```\n\n**With data science stack**:\n```bash\npip install streamlit-notebook[datascience]\n```\n\n**With ai agent dependencies**:\n```bash\npip install streamlit-notebook[agent]\n```\n\n**documents \u0026 web scrapping dependencies**:\n```bash\npip install streamlit-notebook[documents]\n```\n\n**With ai agent + advanced document reading capabilities**:\nSo that the agent can read a wider range of sources (various file types, web pages, ...).\n```bash\npip install streamlit-notebook[agent-full]\n```\n\n**What's included:**\n\n- **Core**: `streamlit`, `streamlit-code-editor`, `asttokens`, `python-dotenv`,`filetype`,`pydub`\n- **datascience** extra: `matplotlib`, `numpy`, `seaborn`, `pandas`, `pillow`, `openpyxl`, `requests`, `pydub`, `graphviz`, `altair`, `plotly`, `bokeh`, `pydeck`, `scipy`, `sympy`, `scikit-learn`, `vega-datasets`\n- **agent** extra: `openai`,`numpy`,`pydantic`,`tiktoken`,`regex`,`pyyaml`,`prompt-toolkit`\n- **documents** extra: `requests`,`beautifulsoup4`,`lxml`,`trafilatura`,`PyPDF2`,\n`python-docx`,`odfpy`,`pillow`,`selenium`,`get-gecko-driver`\n\n## Quick Start\n\n### Create a notebook\n\n**Option 1: Start from the UI**\n```bash\nst_notebook  # Opens empty notebook interface\n```\nCreate cells interactively, then click \"Save notebook\" to save as a `.py` file.\n\n**Option 2: Write the file directly**\n```python\n# analysis.py\nfrom streamlit_notebook import st_notebook\nimport streamlit as st\n\n# Create a notebook instance\nnb = st_notebook(title='analysis')\n\n# Define cells\n@nb.cell(type='code')\ndef setup():\n    import pandas as pd\n    df = pd.read_csv(\"data.csv\")\n    print(f\"Loaded {len(df)} rows\")\n\n@nb.cell(type='code', reactive=True)\ndef explore():\n    col = st.selectbox(\"Column\", df.columns)\n    st.line_chart(df[col])\n\n# Render the notebook\nnb.render()\n```\n\n### Run it\n\nYou can open it with the st_notebook entry point\n\n```bash\n\nst_notebook analysis.py          # Development mode - full notebook interface\nst_notebook analysis.py -- --app # Locked app mode - clean interface, code cells hidden\n```\n\nOr run it directly with Streamlit! (same result)\n```bash\nstreamlit run analysis.py          # Development mode\nstreamlit run analysis.py -- --app # Locked app mode\n```\n\nThe main difference lies in that streamlit-notebook won't allow you to save changes to the notebook file on which `streamlit run` is currently looping on, which isn't a limitation if you open it with the dedicated `st_notebook` entry point.\n\n### How it works?\n\nA bit of magic needs to happen under the hood to make it possible. Fell free to skip this section if you're not into the technical details. \n\nHere's a quick overview:\n\nWhen you run `streamlit run analysis.py`, Streamlit will execute the notebook script in an async loop, reruning it after any UI event, so that each run may process the new state of the UI. Each step of the loop is called a `run`, which is a complete execution of the scipt top to bottom. Let's examine what happens during the first run:\n\n- We import the modules and the required `st_notebook` factory.\n- `st_notebook` first attempts to get an existing notebook instance from `st.session_state`. Since it is the first run, none is found, so it creates one with the provided parameters. \n- The `@cell` decorator is used to capture the source code of the functions' bodies and add the corresponding cells to the notebook instance. **This happens only if the notebook instance is not yet 'initialized' (boolean state)**. The decorator is designed to just do nothing if it is (to avoid re-adding the same cells over and over as the script reruns). \n- `nb.render()` sets `nb.initialized=True` if it wasn't, and renders it on screen (full notebook interface).\n\nIn subsequent runs of the script, `st_notebook` will find an existing notebook instance in state and just return it, ignoring the parameters. The `@cell` decorators will this time ignore the cell definitions (already initialized) and the script will merely execute `nb.render()` to refresh the notebook instance. The nice thing is that the notebook instance returned by `st_notebook` need not be the same as the one created in the first run. If some UI interaction switches it in state, the new instance will be rendered instead.\n\nAs a result, you may open another notebook file from the interface. The notebook will load the code in memory, initialize a new notebook instance from it, store it in state, and rerun the app. In the next run `st_notebook` will fetch the NEW instance, and `nb.render()` will show it, all while still looping on the initial file!\n\nThis is why calling `streamlit run analysis.py` still allows you to change notebook live from the interface.\n\nNote: the functions defining the cells will never get called. Doing so would result in errors, as they refer to variables defined out of their local scopes (in other cells!). It's really a nice thing here that python allows to define erroneous function objects, even decorate them, without throwing an exception as long as we don't attempt to call them (lazy evaluation). They still know the file and line range in which they are defined, which is enough for the decorator to retrieve their raw source code. Makes them usable as mere \"code bags\", ie. containers for source code that gets extracted and used elsewhere.\n\n## Notebook Interface - Core Concepts\n\n### Cell Types\n\n**One-shot cells:** Run only when you trigger them manually. Used for imports, initialization, data loading, expensive computations.\n\n**Reactive cells:** Toggle the `Reactive` option on any code cell to make it reactive. These will rerun automatically on every UI interaction and update the python namespace accordingly. Used for widgets and reactive displays.\n\nThis selective reactivity lets you separate expensive setup from interactive exploration.\n\n### Persistent Shell\n\nAll cells execute in an embedded interactive shell (see [Pynteract](https://github.com/B4PT0R/pynteract) for more details), stored in `st.session_state`, that maintains a single long-lived Python session. Unlike regular Streamlit apps that restart from scratch on every rerun, imports, variables, and computed results will persist in the shell's namespace across UI interactions.\n\n**Example:**\n```python\nfrom streamlit_notebook import st_notebook\nimport streamlit as st\n\nnb = st_notebook(title=\"simple_counter\")\n\n@nb.cell(type='code',reactive=False)\ndef cell_0():\n    # One-shot cell - Run it once to initialize the counter\n    counter=0 \n\n\n@nb.cell(type='code', reactive=True)\ndef cell_1():\n    # Reactive cell - Reruns on every UI interaction\n    if st.button(\"Increment\"):\n        counter+=1\n    st.write(f\"Current counter value: {counter}\")\n\nnb.render()\n```\n\nSince cell_0 doesn't rerun spontaneously, the counter is not reset to 0 on every interaction. cell_1 will thus work with the last known counter value (persisted in the shell's namespace), and update it when the button is clicked.\n\n### 100% Standard Streamlit API\n\nEvery Streamlit widget, chart, and component works out of the box in reactive cells. Just copy-paste your existing Streamlit code there.\n\n```python\n@nb.cell(type='code', reactive=True)\ndef widgets():\n    # Standard Streamlit code - nothing new to learn\n    value = st.slider(\"Select value\", 0, 100)\n    st.metric(\"Current value\", value)\n    st.bar_chart([value, value*2, value*3])\n```\n\nStreamlit Notebook doesn't do anything too hacky with Streamlit's internals and only uses the stable Streamlit API for its own functionning. You can think of it as an extension, rather than a replacement. It will adapt seamlessly to any future versions of Streamlit you may want to install, thus letting you benefit from new widgets or features in your notebooks.\n\n*Note*: It uses the new `width='stretch'` instead of the now deprecated `use_container_width=True` parameter to control its own widgets layout. It also supports the `scope` parameter in its rerun mechanism as well as `run_every` for fragments, thus requiring Streamlit 1.52.0 or higher.\n\n## Real-World Example\n\nBuilding a stock price analysis dashboard. This example uses real data from the vega_datasets package (included in the datascience stack) and can be copy-pasted and run directly:\n\n```python\nfrom streamlit_notebook import st_notebook\nimport streamlit as st\n\nnb = st_notebook(title='stock_dashboard')\n\n@nb.cell(type='code')\ndef setup():\n    import pandas as pd\n    import altair as alt\n    from vega_datasets import data\n\n@nb.cell(type='code')\ndef load_data():\n    df = data.stocks()\n    df['date'] = pd.to_datetime(df['date'])\n    print(f\"Loaded {len(df):,} records for {df['symbol'].nunique()} stocks\")\n\n@nb.cell(type='code', reactive=True)\ndef filters():\n    st.markdown(\"### Stock Analysis Dashboard\")\n    symbols = st.multiselect(\"Select stocks\", df['symbol'].unique(), default=['AAPL', 'GOOG'])\n    date_range = st.date_input(\"Date range\", [df['date'].min(), df['date'].max()])\n\n@nb.cell(type='code', reactive=True)\ndef dashboard():\n    filtered = df[df['symbol'].isin(symbols)] if symbols else df\n    if date_range and len(date_range) == 2:\n        filtered = filtered[(filtered['date'] \u003e= pd.Timestamp(date_range[0])) \u0026\n                           (filtered['date'] \u003c= pd.Timestamp(date_range[1]))]\n\n    col1, col2, col3 = st.columns(3)\n    with col1:\n        st.metric(\"Selected Stocks\", len(symbols))\n    with col2:\n        st.metric(\"Avg Price\", f\"${filtered['price'].mean():.2f}\")\n    with col3:\n        st.metric(\"Total Records\", f\"{len(filtered):,}\")\n\n    chart = alt.Chart(filtered).mark_line().encode(\n        x='date:T',\n        y='price:Q',\n        color='symbol:N'\n    ).properties(height=400)\n    st.altair_chart(chart, width='stretch');\n\nnb.render()\n```\n\n## Easy deployment\n\nOnce you're done working on your notebook you may run it using:\n\n```bash\nstreamlit run stock_dashboard.py -- --app\n```\nNow the same file runs as a locked production app with restricted interface and no visible/editable code.\nThis prevents untrusted users to run arbitrary code in the cloud environment.\nYour notebook can thus safely be published as an online app without more overhead.\n\nThe dedicated environment variable does the same as the flag:\n\n```bash\nexport ST_NOTEBOOK_APP_MODE=true # or use a .env file\nstreamlit run stock_dashboard.py\n```\n\nUseful when you can't modify the command directly (e.g., in Streamlit cloud platform).\n\n## Development Features\n\n### Two Modes\n\n**Notebook Mode** (development):\n- Code editor for each cell\n- Cell management (create, run, delete, insert, change type, reorder)\n- Execution controls (Run Next, Run All, Restart Session, Clear All Cells)\n- Save/Open notebooks\n- Demo notebooks library\n\n**App Mode** (deployment):\n- Restricted interface\n- Code editors hidden\n- Interactive widgets remain functional\n- Clean Streamlit app appearance\n\nIn development, you may toggle between modes with the `app view` switch in the sidebar. In production, just set the `ST_NOTEBOOK_APP_MODE` or use the `--app` flag to prevent users toggling back to notebook mode.\n\n### File Management\n\n**From the sidebar:**\n- **New** button: creates a new notebook from scratch.\n- **Save** button: saves notebook to `./notebook_title.py`\n- **Open** button: dropdown of all `.py` notebooks in current directory, or drag/drop/browse files\n- **Demo notebooks**: load pre-built example notebooks from the library\n\n**From code:**\nSee the Programmatic API section for file operations via code cells.\n\n### Rich Content\n\n**Rich display**\n\nCell results are automatically displayed with pretty formatting when possible (anything that `st.write` can handle).\n\nControl how expression results appear:\n- `all` - show every expression result\n- `last` - show only the last expression (default)\n- `none` - suppress automatic display\n\nConfigurable in the sidebar settings.\n\nYou may also :\n- add a trailing `;` at the end of a line to bypass automatic display\n- use the `display()` function to selectively display a given result. More on this below.\n\n### Advanced Display Control with `display()`\n\nThe `display()` function provides full control over how results are rendered, with access to any Streamlit display backend and all its parameters.\n\n**Basic usage:**\n```python\n@nb.cell(type='code')\ndef load_data():\n    import pandas as pd\n    df = pd.read_csv('data.csv')\n\n    # Simple display (uses st.write by default)\n    display(df)\n```\n\nUnder the hood, `display(obj, backend='write', **kwargs)` is mostly equivalent to `st.\u003cbackend\u003e(obj, **kwargs)`\n\n**Choose any Streamlit backend:**\n```python\n@nb.cell(type='code', reactive=False)\ndef visualize():\n    data = {'name': 'Alice', 'age': 30, 'city': 'Paris'}\n\n    # Use st.json with expansion control\n    display(data, backend='json', expanded=True)\n\n    # Use st.code with syntax highlighting\n    code = \"def hello():\\n    return 'world'\"\n    display(code, backend='code', language='python')\n\n    # Use st.dataframe with additional parameters\n    import pandas as pd\n    df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})\n    display(df, backend='dataframe',\n            height=400,\n            width='stretch',\n            hide_index=True)\n```\n\n**Available backends:**\nAny Streamlit display function works (without the `st.` prefix):\n- `'write'` - Smart auto-rendering (default)\n- `'json'` - JSON viewer with expansion control\n- `'dataframe'` - Interactive DataFrame with custom height, column config, etc.\n- `'table'` - Static table display\n- `'code'` - Syntax-highlighted code\n- `'markdown'` - Rendered markdown with optional HTML\n- `'text'` - Plain text\n- `'plotly_chart'`, `'altair_chart'`, `'pyplot'` - Chart displays with container options\n- Any other `st.*` display function\n\n**Why use `display()` over `st.*()`?**\n\n`display` is designed to store the result and parameters in the cell, so that the notebook can automatically redisplay it after reruns without re-executing the cell. Direct `st.*()` commands need to rerun to stay on screen and thus work only in reactive cells.\n\nThis is particularly powerful in **non-reactive cells** where you get the performance of one-time execution combined with rich, customizable output that persists across reruns.\n\nIt also works seamlessly in reactive cells, where it behaves exactly like `st.*()` but with the added benefit of storing the result in the cell, useful for inspection and debugging.\n\n### Markdown/HTML cells \n\nAdd rich formatted text or layouts to your notebook with Markdown and HTML cells. They support variable interpolation using the `\u003c\u003cany_expression\u003e\u003e` syntax. The expression will be evaluated and replaced in the code.\nIf the value changes, the displayed content will change as well.\n\n```markdown\n# Analysis Results\n\nWe loaded \u003c\u003c len(df) \u003e\u003e rows.\nThe mean value is \u003c\u003c df['value'].mean() \u003e\u003e.\n```\n\n### System commands and magics\n\nIpython style commands and magics are supported.\nLet's demonstrate this by showing a simple example:\n`display(obj, backend='write', **kwargs)` is mostly equivalent to `st.\u003cbackend\u003e(obj, **kwargs)`\n```python\n#Cell 1\n\n#define a new magic\n@magic\ndef upper(text_content):\n    if text_content:\n        return text_content.upper()\n\n#call it in a single line starting with % and the name of the magic function\n%upper This is a test!\n```\nResult:\n```\nTHIS IS A TEST!\n```\n\nThe magic input is always processed as a string (the rest of the line following the %\u003ccommand\u003e) but supports string templating\n\n```python\n%upper os.listdir()\n```\nResult:\n```\nOS.LISTDIR()\n```\n\nvs.\n\n```python\n%upper {os.listdir()}\n```\nResult:\n```\n['ANALYSIS.PY', 'DATA.CSV', 'README.MD']\n```\n\n\n\nWith `%%`, the whole cell starting at second line is considered the magic input.\n\n```python\n#Cell 2\n%%upper\nAll the content of\nthe cell goes in the\nmagic input\n```\nResult:\n```\nALL THE CONTENT OF\nTHE CELL GOES IN THE\nMAGIC INPUT\n```\n\nNote: A subset of most commonly used IPython magics are available.\n\nTo run a system command, use `!` or `!!`.\n\n```python\n!echo \"Hello World\"\n```\nStdout:\n```\nHello World\n```\n\n`!!` captures and returns the result instead of streaming to stdout (stderr still streams):\n```python \nresult = !!echo \"Hello World\"\nresult\n```\nResult:\n```\nHello World\n```\n\nTo run a full-cell bash script use the `%%bash` cell magic.\n\nFor more information about `Pynteract` capabilities as a shell, refer [to the project's repo]().\n\nDisclaimer: The shell is *NOT* an exact replica of Ipython and external API/internal implementation will differ. My goal is to provide a practical and versatile coding environment matching the common needs of interactive programming. Yet, you might encounter situations where you feel like some useful features of Ipython are missing, if so please add a feature request.\n\n## Advanced Features\n\n### Streamlit Fragments\n\nYou can enable the \"Fragment\" option in the Advanced settings panel of a reactive cell to run the cell as a [Streamlit fragment](https://docs.streamlit.io/library/api-reference/performance/st.fragment) for faster, scoped updates:\n\n```python\n@nb.cell(type='code', reactive=True, fragment=True)\ndef fast_widget():\n    # Only this cell reruns on interaction\n    value = st.slider(\"Value\", 0, 100)\n    st.write(f\"Selected: {value}\")\n```\n\nFragments isolate code reruns and UI updates to just the fragment's scope. When a widget defined inside a fragment triggers a UI event, only that fragment's code is rerun and its UI updated.\n\nBeware that widgets in other reactive cells on the page won't refresh even if they depend on variables changed by the fragment.\nSo, in general, it's better to group in a same fragment subsets of widgets and that are supposed to react to eachother.\n\nNote: A variable that's changed by a fragment is immediately updated in the namespace and can be used elsewhere in the notebook.\n\nTo rerun a fragment cell on every given time delta use the `run_every` parameter\n\n```python\n@nb.cell(type='code', reactive=True, fragment=True, run_every=1)\ndef clock():\n    # This cell will rerun every second, not rerunning uselessly the rest of the notebook interface\n    with st.empty():\n        st.write(datetime.now().strftime(\"%H:%M:%S\"))\n```\n\nThis is useful to monitor background tasks or create \"real-time\" widgets, for instance reading data from sensors or APIs at regular intervals and updating the display accordingly.\n\n### Programmatic API\n\nThe notebook instance is exposed in the shell's namespace as `__notebook__`, enabling full programmatic control from the shell or code cells.\n\nThis level of programmatic control is quite unique to streamlit-notebook and enables workflows not easily achievable in Jupyter or traditional notebooks.\n\nThis is especially powerful for automation, AI agents, and dynamic notebook generation.\n\n#### Accessing the Notebook\n\n```python\n# From any code cell\nnb = __notebook__\n\n# Access notebook properties\nprint(f\"Title: {nb.config.title}\")\nprint(f\"Number of cells: {len(nb.cells)}\")\nprint(f\"App mode: {nb.config.app_mode}\")\n```\n\n#### Creating Cells\n\n```python\n# Create a new code cell\ncell = nb.new_cell(\n    type=\"code\",\n    code=\"import pandas as pd\\ndf = pd.DataFrame({'x': [1,2,3]})\",\n    reactive=False,\n    fragment=False,\n    run_every=None\n)\n\n# Create a markdown cell\nmd_cell = nb.new_cell(\n    type=\"markdown\",\n    code=\"# Results\\nThe data has \u003c\u003clen(df)\u003e\u003e rows.\"\n)\n\n# Create an HTML cell\nhtml_cell = nb.new_cell(\n    type=\"html\",\n    code=\"\u003cdiv style='color: red;'\u003e\u003c\u003cresult\u003e\u003e\u003c/div\u003e\"\n)\n```\n\n#### Modifying Cells\n\n```python\n# Access cells by index or key\nfirst_cell = nb.cells[0]\nspecific_cell = nb.get_cell(\"my_cell_key\")\n\n# Modify cell code (automatically updates UI)\nfirst_cell.code = \"import numpy as np\\nprint('Updated!')\"\n\n# Change cell type\nfirst_cell.type = \"markdown\"  # \"code\", \"markdown\", or \"html\"\n\n# Toggle cell options\nfirst_cell.reactive = True\nfirst_cell.fragment = True\n\n# Get cell properties\nprint(f\"Cell ID: {first_cell.id}\")\nprint(f\"Cell index: {first_cell.index}\")\nprint(f\"Cell type: {first_cell.type}\")\nprint(f\"Has run: {first_cell.has_run_once}\")\n```\n\n#### Running Cells\n\n```python\n# Run a specific cell\ncell.run()\n\n# Run all cells in order\nnb.run_all_cells()\n\n# Run the next unexecuted cell\nnb.run_next_cell()\n```\n\n#### Managing Cell Order\n\n```python\n# Move cells up/down\nfirst_cell.move_down()\nlast_cell.move_up()\n\n# Change cell position directly\ncell.index = 2  # Move to position 2\n\n# Insert new cells relative to existing ones\ncell.insert_above()  # Inserts a new code cell above (default: empty code cell)\ncell.insert_below()  # Inserts a new code cell below (default: empty code cell)\n\n# Insert with custom parameters\nnew_cell = cell.insert_above(type=\"markdown\", code=\"# My Title\")  # Returns the new cell\nnew_cell = cell.insert_below(type=\"code\", code=\"print('hello')\", reactive=True)\n```\n\n#### Deleting Cells\n\n```python\n# Delete a specific cell\ncell.delete()\n\n# Delete by key\nnb.delete_cell(\"cell_key\")\n\n# Clear all cells\nnb.clear_cells()\n\n# Reset a cell (clear outputs and execution state)\ncell.reset()\n```\n\n#### Session Management\n\n**Restart the Python session:**\n\nClear the namespace and restart the Python interpreter to start fresh.\n\n```python\nnb.restart_session()\n```\n\n**Quit the application:**\n\nCleanly shutdown the Streamlit server. This performs cleanup operations and then terminates the server process.\n\n```python\nnb.quit()\n```\n\nThis can be useful for automation scripts or when the AI agent needs to close the application at the user's request.\n\n**Smart rerun control:**\n\nThe notebook provides an improved rerun API with flexible timing control, fragment support (Streamlit 1.52+), and better compatibility.\n\nSignature: `rerun(scope:Literal[\"app\", \"fragment\"] = \"app\", wait:bool|float = True)`\n\n```python\n\nnb=__notebook__\n\n# Full app rerun (default)\nnb.rerun()  # wait = True : Soft non-interrupting rerun resolved at end of current cycle\n```\n\nThis requests a rerun at the end of the current Streamlit cycle, letting it finish executing without interrupting subsequent operations.\n\n```python\n# Fragment-scoped rerun (Streamlit 1.52+)\n@st.fragment\ndef my_fragment():\n    if st.button(\"Refresh\"):\n        nb.rerun(scope = \"fragment\")  # Only reruns this fragment\n```\n\nFragment reruns are immediate and don't use delay management since they're typically fast operations.\n\nThe `wait` parameter controls the timing of the rerun. If you request a delay, and the script reaches end of cycle before the delay expires, the rerun will wait until the delay is over before executing.\n\n```python\n# Delayed rerun\nnb.rerun(wait=1.5)\n```\n\nThis is useful to ensure animations or toasts display have time to complete before the page refreshes.\n\n```python\n# Hard rerun (immediate)\nnb.rerun(wait=False)\n```\n\nTriggers an immediate rerun, equivalent to standard `st.rerun()` where it doesn't fail (e.g., in widget callbacks). In circumstances where `st.rerun()` would fail, it falls back to requesting a soft rerun as soon as possible (cancelling any pending delays).\n\n**Note:** In the notebook environment, `st.rerun()` is automatically upgraded to use this enhanced implementation with delay management and fragment support. You can use it directly or via `nb.rerun()` - both work identically.\n\n**Control pending reruns with `wait()`:**\n\nThe `wait()` function lets you control any pending reruns without requesting one.\n\n```python\n# Somewhere in the code: request a rerun\nnb.rerun()\n\n# ...\n\n# Somewhere else : Request a delay before resolving the pending rerun\nst.balloons()\nnb.wait(2.0)  # Ensures the pending rerun waits 2 seconds from this point\n```\n\nThe parameter works similarly to `rerun()`:\n- `wait(2.0)` - Add a 2-second delay before any pending rerun\n- `wait()` or `wait(True)` or `wait(0)` - Do nothing (no additional delay)\n- `wait(False)` - Execute the pending rerun immediately, cancelling any previous requested delays\n\n```python\nnb.rerun(wait=5.0)  # Request rerun with 5 second delay\n# ... some code ...\nnb.wait(False)  # Changed your mind - execute the rerun now!\n```\n\n#### File Operations\n\n```python\n# Save notebook to file\nnb.save()  # Saves to default location (./notebook_title.py)\nnb.save(\"custom_name.py\")  # Save with custom filename\n\n# Open/load a notebook\nnb.open(\"my_notebook.py\")\nnb.open() # Opens a new empty notebook\n\n# Check if a file is a valid notebook\nif nb.is_valid_notebook(source_code):\n    nb.open(source_code)\n```\n\n#### Notifications\n\n```python\n# Show toast notifications with guaranteed visibility\nnb.notify(\"Cell executed successfully!\", icon=\"✅\")\nnb.notify(\"Error occurred\", icon=\"⚠️\", delay=2.0)\n```\n\n#### Converting to Python Code\n\n```python\n# Get the complete Python code representation\npython_code = nb.to_python()\nprint(python_code)  # Shows the @nb.cell decorated version\n```\n\n#### Inspecting Notebook State\n\nGet complete JSON-serializable state of the notebook, including all information about cells and their execution state:\n\n```python\n# Get full notebook info (includes notebook settings and cell states)\ninfo = nb.get_info()  # minimal=False by default\n\n# Access notebook metadata\nprint(f\"Notebook: {info['config']['title']}\")\nprint(f\"Cell count: {info['cell_count']}\")\nprint(f\"App mode: {info['config']['app_mode']}\")\n\n# Iterate through cells\nfor cell_state in info['cells']:\n    print(f\"Cell {cell_state['id']} ({cell_state['type']}):\")\n    print(f\"  Has run: {cell_state['has_run_once']}\")\n    print(f\"  Code length: {len(cell_state['code'])} chars\")\n\n    if cell_state.get('stdout'):\n        print(f\"  Stdout: {cell_state['stdout'][:50]}...\")\n\n    if cell_state.get('exception'):\n        print(f\"  Error: {cell_state['exception']['message']}\")\n\n# Get minimal info (only cell definitions, no execution state)\nminimal_info = nb.get_info(minimal=True)\n# Includes: notebook metadata + minimal cell data (key, type, code, reactive, fragment, run_every)\n\n# Get individual cell state\ncell = nb.cells[0]\nfull_state = cell.to_dict(minimal=False)\n# Includes: id, index, language, has_run_once, visible, stdout,\n# stderr, results, exception\n\nminimal_state = cell.to_dict()  # minimal=True by default\n# Only: key, type, code, reactive, fragment\n\n# Serialize to JSON for AI agents or external tools\nimport json\ncontext = json.dumps(nb.get_info(), indent=2)\n```\n\n#### API Reference\n\n**Notebook Methods:**\n- `new_cell(type, code, reactive, fragment, run_every)` - Create a new cell\n- `get_cell(index_or_key)` - Get cell by position or key\n- `get_info(minimal)` - Get complete notebook info including settings and cell states (default: full state)\n- `delete_cell(key)` - Remove a cell by key\n- `clear_cells()` - Remove all cells\n- `run_all_cells()` - Execute all cells in order\n- `run_next_cell()` - Execute the next unexecuted cell\n- `restart_session()` - Clear namespace and restart Python session\n- `quit()` - Cleanly shutdown the Streamlit server\n- `rerun(scope, wait)` - Trigger a Streamlit rerun (scope: \"app\" or \"fragment\", wait: True/False/float)\n- `wait(delay)` - Control pending reruns (delay, execute now, or do nothing)\n- `notify(message, icon, delay)` - Show toast notification\n- `save(filepath)` - Save notebook to file\n- `open(source)` - Load notebook from file or source code. If no source is provided, creates a new empty notebook.\n- `to_python()` - Get Python code representation\n- `is_valid_notebook(source)` - Check if source is valid notebook\n\n**Cell Methods:**\n- `run()` - Execute the cell\n- `reset()` - Clear outputs and execution state\n- `move_up()` - Move cell up one position\n- `move_down()` - Move cell down one position\n- `insert_above(type, code, reactive, fragment, run_every)` - Insert new cell above (returns new cell). Defaults: type=\"code\", code=\"\", reactive=False, fragment=False, run_every=None\n- `insert_below(type, code, reactive, fragment, run_every)` - Insert new cell below (returns new cell). Defaults: type=\"code\", code=\"\", reactive=False, fragment=False, run_every=None\n- `delete()` - Remove this cell\n- `to_dict(minimal)` - Get dictionary representation (default: minimal, set minimal=False for full state)\n\n**Cell Properties:**\n- `code` (read/write) - Cell code content\n- `type` (read/write) - Cell type: \"code\", \"markdown\", or \"html\"\n- `reactive` (read/write) - Auto-rerun on UI changes\n- `fragment` (read/write) - Run as Streamlit fragment\n- `run_every` (read/write) - Auto-rerun interval in seconds (requires fragment=True, Streamlit 1.52+)\n- `index` (read/write) - Cell position in notebook\n- `key` (read-only) - Unique cell identifier\n- `id` (read-only) - Readable cell identifier combining index and key like `Cell[index](key)`\n- `language` (read-only) - Cell language (\"python\" or \"markdown\")\n- `has_run_once` (read-only) - Whether cell has executed with current code\n\n**Notebook Properties:**\n- `cells` (list) - All cells in the notebook\n- `title` (str) - Notebook title\n- `app_mode` (bool) - Whether in locked app mode\n- `shell` - Python shell instance\n- `current_cell` - Currently executing cell\n\n## AI Agent Integration\n\nStreamlit Notebook includes a full-featured AI agent powered by OpenAI that provides intelligent assistance with full control over the notebook environment.\n\n### Installation\n\nInstall with agent support:\n\n```bash\npip install streamlit-notebook[agent]\n```\n\nFor advanced document reading capabilities (PDF, DOCX, web pages, etc.):\n\n```bash\npip install streamlit-notebook[agent-full]\n```\n\n### Features\n\nThe AI agent comes with powerful capabilities:\n\n- **Full Notebook Control**: Create, edit, delete, and run code cells programmatically\n- **Code Execution**: Run Python code and see results in real-time\n- **Vision Support**: Analyze images, charts, and visual content\n- **Voice Interaction**: Optional voice input/output for hands-free operation\n- **Document Reading**: Read and analyze various file formats (PDF, DOCX, Excel, etc.)\n- **Web Scraping**: Fetch and analyze content from web pages\n- **Tool System**: Extensible architecture for adding custom capabilities\n\n### Quick Start\n\n1. **Set your OpenAI API key** in your .bashrc or a `.env` file:\n   ```bash\n   OPENAI_API_KEY=sk-...\n   ```\n\n2. **Enable the agent** in your notebook by clicking the chat icon in the sidebar\n\n3. **Start chatting** - the agent can help you write code, analyze data, create visualizations, and more\n\n### Accessing the Agent Programmatically\n\nThe agent is available in the shell namespace as `__agent__`:\n\n```python\n@nb.cell(type='code')\ndef interact_with_agent():\n    # Access agent configuration\n    print(f\"Current model: {__agent__.config.model}\")\n\n    # Define and register a custom tool\n    def my_custom_tool(param: str) -\u003e str:\n        \"\"\"\n        description: A custom tool for processing input parameters\n        parameters:\n            param:\n                type: string\n                description: The parameter to process\n        required:\n            - param\n        \"\"\"\n        return f\"Processed: {param}\"\n\n    # Register the tool (auto-extracts metadata from YAML docstring)\n    __agent__.add_tool(my_custom_tool)\n\n    print(\"Custom tool registered!\")\n```\n\n### Agent Capabilities\n\n**Code Generation \u0026 Execution:**\n- Ask the agent to create cells with specific functionality\n- Agent can run cells and see the output\n- Automatically handles errors and suggests fixes\n\n**Data Analysis:**\n- Upload datasets and ask for analysis\n- Agent creates visualizations and statistical summaries\n- Iteratively refine analysis based on conversation\n\n**Documentation:**\n- Agent reads markdown/HTML cells\n- Can reference previous cell outputs\n- Maintains context throughout the conversation\n\n**Voice Mode** (optional):\n- Enable in agent settings\n- Speak your requests naturally\n- Agent responds with voice output\n- Great for hands-free coding sessions\n\n### Configuration\n\nAccess agent settings through the sidebar:\n\n- **Model Selection**: Choose amongst a variety of OpenAI models (gpt-5.1, gpt-5.1-mini, etc.)\n- **Temperature**: Control response creativity\n- **Token Limits**: Configure context and completion limits\n- **Vision**: Toggle image/chart analysis\n- **Voice**: Enable/disable voice interaction\n- **Custom System Prompt**: Customize agent behavior\n\n### Example Workflow\n\n```python\n# Natural language data analysis workflow:\n# 1. User uploads CSV through sidebar\n# 2. Asks agent: \"Analyze this dataset and create a dashboard\"\n# 3. Agent:\n#    - Creates a cell to load and inspect the data\n#    - Creates cells for data cleaning\n#    - Generates multiple visualization cells\n#    - Adds markdown cells explaining findings\n#    - All without user writing any code!\n```\n\n### Best Practices\n\n**Security:**\n- Never share notebooks containing API keys\n- Use st.secrets for sensitive data\n- Agent respects app mode - won't be loaded in production\n\n**Performance:**\n- Agent responses count toward OpenAI API usage\n- Use specific requests for faster responses\n- Complex tasks may require multiple iterations\n\n**Collaboration:**\n- Agent-generated cells are regular cells - edit freely\n- Mix agent-created and manual cells seamlessly\n- Agent learns from conversation context\n\n### Advanced: Custom Tools\n\nExtend the agent with domain-specific capabilities:\n\n```python\n@nb.cell(type='code')\ndef add_custom_tools():\n\n    # Option 1: Direct function call\n    def analyze_sentiment(text: str) -\u003e str:\n        \"\"\"\n        description: Analyze the sentiment of given text\n        parameters:\n            text:\n                type: string\n                description: The text to analyze for sentiment\n        required:\n            - text\n        \"\"\"\n        # Your implementation here\n        return \"positive\"\n\n    __agent__.add_tool(analyze_sentiment)\n\n    # Option 1: Using decorator syntax\n    @__agent__.add_tool\n    def fetch_stock_price(ticker: str) -\u003e dict:\n        \"\"\"\n        description: Fetch current stock price for a given ticker symbol\n        parameters:\n            ticker:\n                type: string\n                description: Stock ticker symbol (e.g., AAPL, GOOGL)\n        required:\n            - ticker\n        \"\"\"\n        # Your implementation here\n        return {\"ticker\": ticker, \"price\": 150.00}\n\n    st.success(\"Custom tools registered!\")\n```\n\n**Key Points:**\n- **YAML Docstrings**: Tool metadata is automatically extracted from the function's docstring in YAML format\n- **Decorator Syntax**: Use `@__agent__.add_tool` for clean, declarative tool registration\n- **Direct Call**: Use `__agent__.add_tool(func)` for conditional or dynamic registration\n\nNow the agent can use these tools in its responses!\n\n## CLI Options\n\nThe schema for passing CLI arguments is:\n\n`st_notebook Optional[notebook_file] Optional[options_1] -- Optional[options_2]`\n\nor equivalently,\n\n`streamlit run [notebook_file] Optional[options_1] -- Optional[options_2]`\n\nOptions before `--` are for Streamlit (e.g., `--server.port 8080`), options after `--` are for your script (e.g., `--app`).\n\nExample with both:\n```bash\nst_notebook analysis.py --server.port 8080 -- --app  # Custom port + app mode\n```\n\n**Custom flags:** You can pass your own flags after `--` to implement custom behavior:\n```bash\nstreamlit run dashboard.py -- --app --data-source=production --debug\n```\n\nThen somewhere in your notebook:\n```python\nimport sys\n\n# sys.argv contains only [options_2] group of CLI arguments (as a single string)\n\n# Check for custom flags\nis_debug = '--debug' in sys.argv\ndata_source = next((arg.split('=')[1] for arg in sys.argv if arg.startswith('--data-source=')), 'dev')\n\nif is_debug:\n    st.write(f\"Debug mode enabled, using {data_source} data source\")\n```\n\n**Built-in flags:**\n\n- `--app`: Locks the notebook in app mode (production mode) where users cannot edit cells or toggle back to edit mode\n- `--no-quit`: Disables the quit button and prevents programmatic shutdown via `nb.quit()`. Useful for cloud deployments where the server should not be terminated by users\n\nExample:\n```bash\n# Deploy with app mode and disable quit functionality\nst_notebook dashboard.py -- --app --no-quit\n```\n\nAlternatively, you can use environment variables:\n```bash\n# Using environment variables\nST_NOTEBOOK_APP_MODE=true ST_NOTEBOOK_NO_QUIT=true st_notebook dashboard.py\n```\n\n## Deployment\n\n### Local Testing\n\nFirst make sure your notebook looks and runs nice as an app. \n\n```bash\n# Test in locked app mode (production simulation)\nst_notebook my_notebook.py -- --app\n```\n\n### Streamlit Cloud\n\n1. Create `requirements.txt`:\n    ```\n    streamlit-notebook\n    # ... other dependencies\n    ```\n\n2. Create `.streamlit/config.toml` (optional - for page config):\n    ```toml\n    [theme]\n    primaryColor = \"#F63366\"\n    backgroundColor = \"black\"\n    ```\n\n3. Create `.env` file to enable locked app mode and disable quit:\n    ```bash\n    ST_NOTEBOOK_APP_MODE=true\n    ST_NOTEBOOK_NO_QUIT=true\n    ```\n\n4. Push to GitHub:\n    ```bash\n    git add my_notebook.py requirements.txt .env\n    git commit -m \"Add dashboard\"\n    git push\n    ```\n\n5. Deploy on [share.streamlit.io](https://share.streamlit.io):\n   - Connect your GitHub repo\n   - Select `my_notebook.py` as main file\n   - Click \"Deploy\"\n\nSince notebooks are just Python files, Streamlit Cloud runs them directly — no wrapper needed.\n\n### Docker\n\n```dockerfile\nFROM python:3.11-slim\n\nWORKDIR /app\n\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY my_notebook.py .\n\nEXPOSE 8501\n\n# Option 1: Use flags for app mode and disabled quit\nCMD [\"streamlit\", \"run\", \"my_notebook.py\", \"--\", \"--app\", \"--no-quit\"]\n\n# Option 2: Use environment variables\n# ENV ST_NOTEBOOK_APP_MODE=true\n# ENV ST_NOTEBOOK_NO_QUIT=true\n# CMD [\"streamlit\", \"run\", \"my_notebook.py\"]\n```\n\nBuild and run:\n```bash\ndocker build -t my-dashboard .\ndocker run -p 8501:8501 my-dashboard\n```\n\nDeploy to AWS ECS, Google Cloud Run, Azure Container Apps, or any container platform.\n\n### Production Best Practices\n\n✅ **Do:**\n- Use one shot cells for expensive operations\n- Test with `--app` flag locally first\n- Setup a `.env` file with `ST_NOTEBOOK_APP_MODE=true` to ensure locked app mode once deployed.\n- Include `streamlit-notebook` and all external dependencies in `requirements.txt`\n- Use `st.secrets` for secrets (API keys, database credentials, etc.)\n\n❌ **Don't:**\n- Allow code editing in production deployments (the user could read `st.secrets` or run malicious scripts)\n- Hardcode API keys or credentials in any file exposed in the public repo.\n- Assume filesystem persistence. Changes you make to the files will be discarded when the container reboots. (use databases/cloud storage instead)\n\n### Multi-Notebook Deployments\n\nDeploy multiple related notebooks in a single repo, allowing users to navigate between them while keeping everything in locked app mode.\n\n**Use case:** A data engineer creates several analysis notebooks (Sales, Customers, Forecasting) and wants to deploy them all as apps with simple navigation.\n\n**Setup:**\n```\nmy-analytics/\n├── .env                          # ST_NOTEBOOK_APP_MODE=true\n├── requirements.txt\n├── sales_analysis.py\n├── customer_segmentation.py\n└── forecasting_model.py\n```\n\n**How it works:**\n- Deploy to Streamlit Cloud pointing to any notebook as the main file\n- Users see \"Open notebook\" dropdown to switch between notebooks\n- `ST_NOTEBOOK_APP_MODE=true` in `.env` ensures ALL notebooks are locked apps\n- Safe for end users—no code editing allowed on any notebook\n\nThis is perfect for demos, dashboards, or sharing multiple analyses with stakeholders without creating separate deployments for each notebook.\n\n## Use Cases\n\n**Data Exploration → Dashboard:** Build analysis interactively with selective reactivity, then deploy as a polished dashboard with the `--app` flag.\n\n**Prototyping → Production:** Develop proof-of-concept in notebook mode with instant feedback, deploy as locked app without rewriting code.\n\n**Interactive Reports:** Blend narrative (Markdown/HTML) with live data, widgets, and dynamic visualizations in a single document.\n\n**Teaching \u0026 Demos:** Create interactive tutorials with executable code cells that students can run and modify step-by-step.\n\n**AI-Assisted Development:** Use the integrated AI agent to generate code, analyze data, create visualizations, and build entire workflows through natural language. The agent has full programmatic control and can be extended with custom tools via `__agent__`.\n\n## Contributing\n\nContributions welcome! File issues for bugs or feature requests. Submit PRs for improvements.\n\n1. Fork the repo\n2. Create a feature branch (`git checkout -b feature/my-idea`)\n3. Commit changes (`git commit -m \"Add feature\"`)\n4. Push (`git push origin feature/my-idea`)\n5. Open a pull request\n\n## License\n\nMIT License—see [LICENSE](LICENSE).\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for detailed version history and release notes.\n\n---\n\n**Develop with notebooks. Deploy as apps.** 🚀\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb4pt0r%2Fstreamlit_notebook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fb4pt0r%2Fstreamlit_notebook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb4pt0r%2Fstreamlit_notebook/lists"}