├── .github └── workflows │ ├── build.yaml │ └── test.yaml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── conda.recipe └── meta.yaml ├── dodo.py ├── panel_server ├── __init__.py └── icons │ └── logo.svg ├── pyproject.toml ├── setup.cfg ├── setup.py └── tox.ini /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: packages 2 | on: 3 | push: 4 | tags: 5 | - 'v[0-9]+.[0-9]+.[0-9]+' 6 | - 'v[0-9]+.[0-9]+.[0-9]+a[0-9]+' 7 | - 'v[0-9]+.[0-9]+.[0-9]+b[0-9]+' 8 | - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' 9 | 10 | jobs: 11 | conda_build: 12 | name: Build Conda Packages 13 | runs-on: 'ubuntu-latest' 14 | defaults: 15 | run: 16 | shell: bash -l {0} 17 | env: 18 | CHANS: "-c pyviz -c conda-forge" 19 | CHANS_DEV: "-c pyviz/label/dev -c conda-forge" 20 | PKG_TEST_PYTHON: "--test-python=py37" 21 | PYTHON_VERSION: "3.7" 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Fetch unshallow 25 | run: git fetch --prune --tags --unshallow -f 26 | - uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - uses: conda-incubator/setup-miniconda@v2 30 | with: 31 | miniconda-version: "latest" 32 | python-version: 3.8 33 | - name: Set output 34 | id: vars 35 | run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT 36 | - name: conda setup 37 | run: | 38 | conda config --set always_yes True 39 | conda install -c pyviz "pyctdev>=0.5" 40 | doit ecosystem_setup 41 | - name: conda build 42 | run: doit package_build $CHANS_DEV $PKG_TEST_PYTHON --test-group=all 43 | - name: conda dev upload 44 | if: (contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc')) 45 | run: doit package_upload --token=${{ secrets.CONDA_UPLOAD_TOKEN }} --label=dev 46 | - name: conda main upload 47 | if: (!(contains(steps.vars.outputs.tag, 'a') || contains(steps.vars.outputs.tag, 'b') || contains(steps.vars.outputs.tag, 'rc'))) 48 | run: doit package_upload --token=${{ secrets.CONDA_UPLOAD_TOKEN }} --label=dev --label=main 49 | pip_build: 50 | name: Build PyPI Packages 51 | runs-on: 'ubuntu-latest' 52 | defaults: 53 | run: 54 | shell: bash -l {0} 55 | env: 56 | CHANS: "-c pyviz -c conda-forge" 57 | CHANS_DEV: "-c pyviz/label/dev -c conda-forge" 58 | PKG_TEST_PYTHON: "--test-python=py37" 59 | PYTHON_VERSION: "3.7" 60 | PPU: ${{ secrets.PPU }} 61 | PPP: ${{ secrets.PPP }} 62 | PYPI: "https://upload.pypi.org/legacy/" 63 | steps: 64 | - uses: actions/checkout@v3 65 | - name: Fetch unshallow 66 | run: git fetch --prune --tags --unshallow -f 67 | - uses: actions/setup-python@v4 68 | with: 69 | python-version: ${{ matrix.python-version }} 70 | - uses: conda-incubator/setup-miniconda@v2 71 | with: 72 | miniconda-version: "latest" 73 | - name: conda setup 74 | run: | 75 | conda config --set always_yes True 76 | conda install -c pyviz "pyctdev>=0.5" 77 | doit ecosystem_setup 78 | doit env_create $CHANS_DEV --python=$PYTHON_VERSION 79 | - name: env setup 80 | run: | 81 | eval "$(conda shell.bash hook)" 82 | conda activate test-environment 83 | doit develop_install $CHANS_DEV -o tests 84 | doit pip_on_conda 85 | - name: pip build 86 | run: | 87 | eval "$(conda shell.bash hook)" 88 | conda activate test-environment 89 | doit ecosystem=pip package_build $PKG_TEST_PYTHON --test-group=all 90 | - name: pip upload 91 | run: | 92 | eval "$(conda shell.bash hook)" 93 | conda activate test-environment 94 | doit ecosystem=pip package_upload -u $PPU -p $PPP -r $PYPI 95 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | # things not included 2 | # language 3 | # notifications - no email notifications set up 4 | 5 | name: pytest 6 | on: 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | test_suite: 13 | name: Pytest on ${{ matrix.python-version }}, ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] 19 | python-version: [3.6, 3.7, 3.8] 20 | timeout-minutes: 10 21 | defaults: 22 | run: 23 | shell: bash -l {0} 24 | env: 25 | PYTHON_VERSION: ${{ matrix.python-version }} 26 | CHANS_DEV: "-c pyviz/label/dev -c conda-forge" 27 | CHANS: "-c pyviz -c conda-forge" 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | steps: 30 | - uses: actions/checkout@v3 31 | with: 32 | fetch-depth: "100" 33 | - uses: actions/setup-python@v4 34 | with: 35 | python-version: ${{ matrix.python-version }} 36 | - uses: conda-incubator/setup-miniconda@v2 37 | with: 38 | miniconda-version: "latest" 39 | - name: Fetch 40 | run: git fetch --prune --tags 41 | - name: conda setup 42 | run: | 43 | conda config --set always_yes True 44 | conda install -c pyviz "pyctdev>=0.5" 45 | doit ecosystem_setup 46 | doit env_create ${{ env.CHANS_DEV}} --python=${{ matrix.python-version }} 47 | - name: doit develop_install 48 | run: | 49 | eval "$(conda shell.bash hook)" 50 | conda activate test-environment 51 | doit list 52 | doit develop_install ${{ env.CHANS_DEV }} -o tests 53 | - name: doit env_capture 54 | run: | 55 | eval "$(conda shell.bash hook)" 56 | conda activate test-environment 57 | doit env_capture 58 | - name: doit test_lint 59 | run: | 60 | eval "$(conda shell.bash hook)" 61 | conda activate test-environment 62 | doit test_lint 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | *.egg-info/ 5 | __pycache__ 6 | .version 7 | 8 | 9 | ## pyctdev 10 | .doit.db 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2021, HoloViz contributors 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the 15 | distribution. 16 | 17 | * Neither the name of PyViz nor the names of its contributors 18 | may be used to endorse or promote products derived from this 19 | software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include panel_server/.version 4 | global-exclude *.py[co] 5 | global-exclude *~ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jupyter Server Proxy for Panel 2 | 3 | When jupyter-panel-proxy is installed and you launch a Jupyter server (Notebook, JupyterLab or JupyterHub), a Panel server will be launched when you visit the `/panel` endpoint of the server. This will show an index of all applications being served, to launch a particular application visit the corresponding endpoint `/panel/`. 4 | 5 | ## Installation 6 | 7 | The `jupyter-panel-proxy` is available from `pip`: 8 | 9 | pip install jupyter-panel-proxy 10 | 11 | and conda: 12 | 13 | conda install -c pyviz jupyter-panel-proxy 14 | 15 | ## Configuration 16 | 17 | The jupyter-panel-proxy provides the ability to configure the proxy server by declaring a `jupyter-panel-proxy.yml` in the directory the Jupyter server is being launched from. The `yaml` file may declare the following keys: 18 | 19 | - `apps` (`list`): A list of applications or glob patterns to serve 20 | - `launcher_entry` (`dict`): A [jupyter-server-proxy launcher entry specification](https://jupyter-server-proxy.readthedocs.io/en/latest/server-process.html#launcher-entry) 21 | - `file_types` (`list(str)`): A list of file types to serve if no explicit apps list is provided 22 | - `exclude_patterns` (`list(str)`): A list of glob/(fnmatch) patterns to exclude specific applications 23 | - `index` (`str`): The path to a Bokeh index template 24 | - `autoreload` (`bool`): Whether to automatically reload user sessions when the application or any of its imports change. 25 | - `static_dirs` (`list`): A list of dicts mapping from server route to the static directory to be served 26 | - `warm` (`bool`): Whether to execute scripts on startup to warm up the server. 27 | - `num_procs` (`int`): Number of worker processes for an app. Using 0 will autodetect number of cores (defaults to 1) 28 | - `oauth_provider` (`str`): The OAuth2 provider to use. 29 | - `oauth-key` (`str`): The OAuth2 key to use 30 | - `oauth-secret` (`str`): The OAuth2 secret to use 31 | - `oauth-redirect-uri` (`str`): The OAuth2 redirect URI 32 | - `oauth_extra_params` (`dict`): Additional parameters to the OAuth provider. 33 | - `oauth_jwt_user` (`str`): The key in the ID JWT token to consider the user. 34 | -------------------------------------------------------------------------------- /conda.recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set sdata = load_setup_py_data() %} 2 | 3 | package: 4 | name: jupyter-panel-proxy 5 | version: {{ sdata['version'] }} 6 | 7 | source: 8 | path: .. 9 | 10 | build: 11 | noarch: python 12 | script: python setup.py install --single-version-externally-managed --record=record.txt 13 | entry_points: 14 | {% for group,epoints in sdata.get("entry_points",{}).items() %} 15 | {% for entry_point in epoints %} 16 | - {{ entry_point }} 17 | {% endfor %} 18 | {% endfor %} 19 | 20 | requirements: 21 | host: 22 | - python {{ sdata['python_requires'] }} 23 | - param >=1.7.0 24 | - setuptools >30.3.0 25 | run: 26 | - python {{ sdata['python_requires'] }} 27 | {% for dep in sdata.get('install_requires',{}) %} 28 | - {{ dep }} 29 | {% endfor %} 30 | test: 31 | imports: 32 | - panel_server 33 | 34 | about: 35 | home: {{ sdata['url'] }} 36 | summary: {{ sdata['description'] }} 37 | license: {{ sdata['license'] }} 38 | -------------------------------------------------------------------------------- /dodo.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file provides a mechanism to map between the semantic commands that any 3 | project has (build docs, run tests, copy examples ... ) and the specific 4 | command that should be run. 5 | 6 | Most of these commands are stored in pyctdev which is essentially a collection 7 | of the pyviz way of doing these actions. Commands that are newer, or specific 8 | to a particular project, will live in this file instead. To see a list of 9 | all the available commands - after installing pyctdev - run: 10 | 11 | $ doit list 12 | """ 13 | 14 | import os 15 | 16 | if "PYCTDEV_ECOSYSTEM" not in os.environ: 17 | os.environ["PYCTDEV_ECOSYSTEM"] = "conda" 18 | 19 | from pyctdev import * # noqa: api 20 | 21 | def task_pip_on_conda(): 22 | """Experimental: provide pip build env via conda""" 23 | return {'actions':[ 24 | # some ecosystem=pip build tools must be installed with conda when using conda... 25 | 'conda install -y pip twine wheel', 26 | # ..and some are only available via conda-forge 27 | 'conda install -y -c conda-forge tox virtualenv', 28 | ]} 29 | -------------------------------------------------------------------------------- /panel_server/__init__.py: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import glob 3 | import pathlib 4 | import yaml 5 | 6 | import param 7 | 8 | __version__ = str( 9 | param.version.Version( 10 | fpath=__file__, 11 | archive_commit="$Format:%h$", 12 | reponame="panel_server", 13 | )) 14 | 15 | EXCLUDE_PATTERNS = ['*setup.py', '*dodo.py', '*.ipynb_checkpoints*'] 16 | 17 | ICON_PATH = str((pathlib.Path(__file__).parent / "icons" / "logo.svg").absolute()) 18 | 19 | LAUNCHER_ENTRY = { 20 | "enabled": True, 21 | "title": "Panel Launcher", 22 | "icon_path": ICON_PATH 23 | } 24 | 25 | DEFAULT_CONFIG = { 26 | 'autoreload': True, 27 | 'file_types': ['ipynb', 'py'], 28 | 'launcher_entry': LAUNCHER_ENTRY 29 | } 30 | 31 | 32 | def _get_config(): 33 | config_path = pathlib.Path('jupyter-panel-proxy.yml') 34 | config = dict(DEFAULT_CONFIG) 35 | if config_path.is_file(): 36 | with open(config_path) as f: 37 | config.update(yaml.load(f.read(), Loader=yaml.BaseLoader)) 38 | return config 39 | 40 | 41 | def _search_apps(config): 42 | base_dir = pathlib.Path('./') 43 | example_dir = base_dir / 'examples' 44 | if example_dir.is_dir(): 45 | base_path = example_dir 46 | else: 47 | base_path = base_dir 48 | apps = [] 49 | for ft in config.get('file_types'): 50 | apps += [str(app) for app in base_path.glob(f'**/*.{ft}')] 51 | return apps 52 | 53 | 54 | def _discover_apps(): 55 | config = _get_config() 56 | if 'apps' in config: 57 | found_apps = [] 58 | for app_spec in config.get('apps', []): 59 | found_apps += glob.glob(app_spec) 60 | else: 61 | found_apps = _search_apps(config) 62 | exclude_patterns = config.get('exclude_patterns', []) + EXCLUDE_PATTERNS 63 | config['apps'] = [ 64 | app for app in found_apps 65 | if not any(fnmatch.fnmatch(app, ep) for ep in exclude_patterns) 66 | ] 67 | return config 68 | 69 | 70 | def _launch_command(port): 71 | config = _discover_apps() 72 | command = ["panel", "serve", *config.get('apps'), "--allow-websocket-origin=*", "--port", f"{port}", "--prefix", "{base_url}panel", "--disable-index-redirect"] 73 | if config.get('autoreload'): 74 | command.append('--autoreload') 75 | if config.get('warm'): 76 | command.append('--warm') 77 | if 'num_procs' in config: 78 | command += ['--num-procs', str(config['num_procs'])] 79 | if 'static_dirs' in config: 80 | command += ['--static-dirs', *config['static_dirs']] 81 | if 'oauth_provider' in config: 82 | from cryptography.fernet import Fernet 83 | from bokeh.util.token import generate_secret_key 84 | command += ['--oauth-provider', config['oauth_provider']] 85 | command += ['--oauth-encryption-key', Fernet.generate_key()] 86 | command += ['--cookie-secret', generate_secret_key()] 87 | if 'oauth_key' in config: 88 | command += ['--oauth-key', config['oauth_key']] 89 | if 'oauth_secret' in config: 90 | command += ['--oauth-secret', config['oauth_secret']] 91 | if 'oauth_redirect_uri' in config: 92 | command += ['--oauth-redirect-uri', config['oauth_redirect_uri']] 93 | if 'oauth_jwt_user' in config: 94 | command += ['--oauth-jtw-user', config['oauth_jwt_user']] 95 | if 'oauth_extra_params' in config: 96 | command += ['--oauth-extra-params', repr(config['oauth_extra_params'])] 97 | if 'index' in config: 98 | command += ['--index', str(pathlib.Path(config['index']).absolute())] 99 | return command 100 | 101 | 102 | def setup_panel_server(): 103 | config = _get_config() 104 | spec = { 105 | 'command': _launch_command, 106 | 'absolute_url': True, 107 | 'timeout': 360 108 | } 109 | if 'launcher_entry' in config: 110 | spec['launcher_entry'] = config['launcher_entry'] 111 | return spec 112 | -------------------------------------------------------------------------------- /panel_server/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 18 | 21 | 22 | 25 | 28 | 29 | 30 | 32 | 33 | 35 | image/svg+xml 36 | 38 | 39 | 40 | 41 | 42 | 45 | 49 | 53 | 57 | 61 | Panel 65 | 66 | 67 | 68 | 69 | 72 | 79 | 81 | 83 | 90 | 96 | 97 | 99 | 106 | 112 | 113 | 115 | 122 | 128 | 129 | 130 | 131 | 139 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "param >=1.7.0", 4 | "setuptools", 5 | "wheel", 6 | ] 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file = LICENSE 3 | 4 | [wheel] 5 | universal = 1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import param 2 | 3 | from setuptools import find_packages, setup 4 | 5 | 6 | extras_require = { 7 | 'build': ['param >=1.7.0', 'setuptools'], 8 | 'tests': [ 9 | 'flake8', 10 | 'twine', 11 | 'rfc3986', 12 | 'keyring' 13 | ], 14 | } 15 | 16 | setup_args = dict( 17 | name="jupyter-panel-proxy", 18 | description='Jupyter Server Proxy for Panel applications', 19 | version=param.version.get_setup_version( 20 | __file__, 21 | "panel_server", 22 | archive_commit="$Format:%h$", 23 | ), 24 | long_description=open('README.md').read(), 25 | long_description_content_type="text/markdown", 26 | author="Julia Signell", 27 | author_email= "developers@holoviz.org", 28 | maintainer= "HoloViz developers", 29 | maintainer_email= "developers@pyviz.org", 30 | url="https://github.com/holoviz/jupyter-panel-proxy", 31 | project_urls = { 32 | "Bug Tracker": "http://github.com/holoviz/jupyter-panel-proxy/issues", 33 | "Documentation": "https://github.com/holoviz/jupyter-panel-proxy/blob/main/README.md", 34 | "Source Code": "https://github.com/holoviz/jupyter-panel-proxy", 35 | }, 36 | platforms=['Windows', 'Mac OS X', 'Linux'], 37 | license='BSD', 38 | classifiers = [ 39 | "License :: OSI Approved :: BSD License", 40 | "Development Status :: 5 - Production/Stable", 41 | "Programming Language :: Python", 42 | "Programming Language :: Python :: 3.6", 43 | "Programming Language :: Python :: 3.7", 44 | "Programming Language :: Python :: 3.8", 45 | "Programming Language :: Python :: 3.9", 46 | "Operating System :: OS Independent", 47 | "Intended Audience :: Science/Research", 48 | "Intended Audience :: Developers", 49 | "Natural Language :: English", 50 | "Topic :: Scientific/Engineering", 51 | "Topic :: Software Development :: Libraries" 52 | ], 53 | python_requires=">=3.6", 54 | install_requires=['jupyter-server-proxy', 'panel >=0.11'], 55 | extras_require=extras_require, 56 | packages=find_packages(), 57 | entry_points={ 58 | 'jupyter_serverproxy_servers': [ 59 | 'panel = panel_server:setup_panel_server', 60 | ] 61 | }, 62 | ) 63 | 64 | if __name__ == '__main__': 65 | setup(**setup_args) 66 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # For use with pyctdev (but should work with tox alone) 2 | 3 | [tox] 4 | envlist = {py36,py37,py38}-{lint,all}-{default}-{dev,pkg} 5 | build = wheel 6 | 7 | [_lint] 8 | description = Lint check python code 9 | deps = .[tests] 10 | commands = flake8 11 | 12 | [_all] 13 | description = Run all the tests 14 | deps = .[tests] 15 | commands = {[_lint]commands} 16 | 17 | [testenv] 18 | changedir = {envtmpdir} 19 | 20 | commands = lint: {[_lint]commands} 21 | all: {[_all]commands} 22 | 23 | deps = lint: {[_lint]deps} 24 | all: {[_all]deps} 25 | 26 | [pytest] 27 | addopts = -v --pyargs 28 | norecursedirs = doc .git dist build _build .ipynb_checkpoints 29 | 30 | [flake8] 31 | include = *.py 32 | exclude = .git,__pycache__,.tox,.eggs,*.egg,doc,dist,build,_build,.ipynb_checkpoints,run_test.py 33 | ignore = E, 34 | W 35 | --------------------------------------------------------------------------------