Repository: OKUA1/juvio
Branch: main
Commit: adb74f5cf9f7
Files: 43
Total size: 75.3 KB
Directory structure:
gitextract_jjcr1hib/
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── juvio/
│ ├── __init__.py
│ ├── content_manager.py
│ ├── converter.py
│ ├── kernel_launcher.py
│ ├── kernelspec/
│ │ └── kernel.json
│ ├── labextension/
│ │ ├── package.json
│ │ └── static/
│ │ ├── 509.8b21d569ebbb8f039bdd.js
│ │ ├── 728.864a8d7732d3e14d284a.js
│ │ ├── remoteEntry.cb75fcc929fa00484f36.js
│ │ ├── style.js
│ │ └── third-party-licenses.json
│ ├── server_extension.py
│ └── shared.py
├── juvio_frontend/
│ ├── .gitignore
│ ├── .prettierignore
│ ├── .yarnrc.yml
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── RELEASE.md
│ ├── babel.config.js
│ ├── install.json
│ ├── jest.config.js
│ ├── package.json
│ ├── pyproject.toml
│ ├── setup.py
│ ├── src/
│ │ ├── __tests__/
│ │ │ └── juvio_frontend.spec.ts
│ │ └── index.ts
│ ├── style/
│ │ ├── base.css
│ │ ├── index.css
│ │ └── index.js
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── ui-tests/
│ ├── README.md
│ ├── jupyter_server_test_config.py
│ ├── package.json
│ ├── playwright.config.js
│ └── tests/
│ └── juvio_frontend.spec.ts
└── pyproject.toml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2025 Oleh Kostromin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include LICENSE
include README.md
include pyproject.toml
# Include all files from the kernelspec and labextension directories
# Use the correct relative paths
recursive-include juvio/kernelspec *
recursive-include juvio/labextension *
# Exclude unnecessary files
global-exclude *.py[cod]
global-exclude __pycache__
global-exclude *.so
global-exclude .git*
global-exclude .ipynb_checkpoints
global-exclude node_modules
================================================
FILE: README.md
================================================
<div align="center">
<img alt="logo" src="https://gist.githubusercontent.com/OKUA1/d6e65e883546021ea774857878fd0537/raw/4de2ea217e25d9ff7b3d2a73899e85665ed7d94c/juvio_logo.svg" height = "250">
</div>
# **Juvio**: reproducible, dependency-aware, and Git-friendly Jupyter Notebooks.
## 🚀 What It Does
- 💡 **Inline Dependency Management**
Install packages right from the notebook:
```python
%juvio install numpy pandas
```
Dependencies are saved directly in the notebook as metadata (PEP 723-style), like:
```python
# /// script
# requires-python = "==3.10.17"
# dependencies = [
# "numpy==2.2.5",
# "pandas==2.2.3"
# ]
# ///
```
- ⚙️ **Automatic Environment Setup**
When the notebook is opened, Juvio installs the dependencies automatically in an ephemeral virtual environment (using `uv`), ensuring that the notebook runs with the correct versions of the packages and Python.
- 📁 **Git-Friendly Format**
Notebooks are converted on the fly to a script-style format using `# %%` markers, making diffs and version control painless:
```python
# %%
%juvio install numpy
# %%
import numpy as np
# %%
arr = np.array([1, 2, 3])
print(arr)
# %%
```
## 🧑💻 How to Use
> [!WARNING]
> This project is currently in **early beta**. It may contain bugs and is subject to change.
> Please [open an issue](../../issues) to report problems or suggest improvements.
**1. Install Juvio:**
```bash
pip install juvio
jupyter labextension enable juvio-frontend
```
**2. Make sure you have uv installed:**
https://docs.astral.sh/uv/getting-started/installation/
**3. Start JupyterLab and create a Juvio Notebook.**
**4. Install necessary packages in the notebook and run your code**
```python
%juvio install ...
```
Dependencies are tracked, environments are reproducible, and your notebook stays Git-clean ✨
**Known issue:** If you experience the error "Notebook does not appear to be JSON", try to lauch the jupyterlab with an additional argument:
```bash
jupyter lab --ServerApp.jpserver_extensions="{'juvio': True}"
```
## Why Use Juvio?
- No additional lock or requirements files are needed
- Guaranteed reproducibility
- Cleaner Git diffs
## Powered By
- `uv` – ultra-fast Python package management
- `PEP 723` – Python inline dependency standards
- `jupytext`-like format for easy version control
================================================
FILE: juvio/__init__.py
================================================
from .server_extension import _load_jupyter_server_extension
def _jupyter_server_extension_points():
return [{"module": "juvio.server_extension"}]
# def _jupyter_labextension_paths():
# return [
# {
# "src": "labextension",
# "dest": "juvio",
# }
# ]
================================================
FILE: juvio/content_manager.py
================================================
import nbformat
import inspect
import aiofiles
from juvio.converter import JuvioConverter
from juvio.shared import FileLock, AsyncFileLock
import os
def _is_juvio_file(path):
return path.endswith(".juvio")
def juvio_get_sync(self, path, content=True, type=None, format=None, **kwargs):
api_path = path.replace("\\", "/").lstrip("/")
full_path = self._get_os_path(api_path)
if not _is_juvio_file(api_path):
return self._original_get(
api_path, content=content, type=type, format=format, **kwargs
)
model = self._original_get(api_path, content=False, **kwargs)
if not content:
model["type"] = "notebook"
return model
try:
with FileLock(full_path):
with open(full_path, "r", encoding="utf-8") as f:
text = f.read()
nb = JuvioConverter.convert_script_to_notebook(text)
nbformat.validate(nb)
model.update(type="notebook", format="json", content=nb)
return model
except Exception as e:
from tornado.web import HTTPError
raise HTTPError(500, f"Error while reading {api_path}: {e}")
def juvio_save_sync(self, model, path, **kwargs):
from datetime import datetime
def format_timestamp(timestamp):
return datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
model_type = model.get("type")
model_content = model.get("content")
api_path = path.replace("\\", "/").lstrip("/")
full_path = self._get_os_path(api_path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
if not _is_juvio_file(api_path) or model_type != "notebook":
return self._original_save(model, api_path, **kwargs)
try:
with FileLock(full_path):
pass # ensures lock before opening
if model_content is not None:
if isinstance(model_content, dict):
nb = nbformat.from_dict(model_content)
else:
nb = model_content
nbformat.validate(nb)
with open(full_path, "r", encoding="utf-8") as f:
existing = f.read()
metadata = JuvioConverter._generate_metadata(
**JuvioConverter.extract_metadata(existing)
)
text = JuvioConverter.convert_notebook_to_script(nb, metadata)
with open(full_path, "w", encoding="utf-8") as f:
f.write(text)
else:
with open(full_path, "w", encoding="utf-8") as f:
f.write(JuvioConverter.create())
stat = os.stat(full_path)
return {
"name": os.path.basename(api_path),
"path": api_path,
"type": "notebook",
"format": None,
"content": None,
"created": format_timestamp(stat.st_ctime),
"last_modified": format_timestamp(stat.st_mtime),
"writable": True,
"mimetype": None,
}
except Exception as e:
from tornado.web import HTTPError
raise HTTPError(500, f"Error while saving {api_path}: {e}")
async def juvio_get_async(self, path, content=True, type=None, format=None, **kwargs):
api_path = path.replace("\\", "/").lstrip("/")
full_path = self._get_os_path(api_path)
if not _is_juvio_file(api_path):
return await self._original_get(
api_path, content=content, type=type, format=format, **kwargs
)
model = await self._original_get(api_path, content=False, **kwargs)
if not content:
model["type"] = "notebook"
return model
try:
async with AsyncFileLock(full_path):
pass # ensures lock before opening
async with aiofiles.open(full_path, "r", encoding="utf-8") as f:
text = await f.read()
nb = JuvioConverter.convert_script_to_notebook(text)
nbformat.validate(nb)
model.update(type="notebook", format="json", content=nb)
return model
except Exception as e:
from tornado.web import HTTPError
raise HTTPError(500, f"Error while reading {api_path}: {e}")
async def juvio_save_async(self, model, path, **kwargs):
from datetime import datetime
def format_timestamp(timestamp):
return datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
model_type = model.get("type")
model_content = model.get("content")
api_path = path.replace("\\", "/").lstrip("/")
full_path = self._get_os_path(api_path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
if not _is_juvio_file(api_path) or model_type != "notebook":
return await self._original_save(model, api_path, **kwargs)
try:
async with AsyncFileLock(full_path):
pass # ensures lock before opening
if model_content is not None:
if isinstance(model_content, dict):
nb = nbformat.from_dict(model_content)
else:
nb = model_content
nbformat.validate(nb)
async with aiofiles.open(full_path, "r", encoding="utf-8") as f:
existing = await f.read()
metadata = JuvioConverter._generate_metadata(
**JuvioConverter.extract_metadata(existing)
)
text = JuvioConverter.convert_notebook_to_script(nb, metadata)
async with aiofiles.open(full_path, "w", encoding="utf-8") as f:
await f.write(text)
else:
async with aiofiles.open(full_path, "w", encoding="utf-8") as f:
await f.write(JuvioConverter.create())
stat = os.stat(full_path)
return {
"name": os.path.basename(api_path),
"path": api_path,
"type": "notebook",
"format": None,
"content": None,
"created": format_timestamp(stat.st_ctime),
"last_modified": format_timestamp(stat.st_mtime),
"writable": True,
"mimetype": None,
}
except Exception as e:
from tornado.web import HTTPError
raise HTTPError(500, f"Error while saving {api_path}: {e}")
def juvio_rename_file(self, old_path, new_path):
old_api_path = old_path.replace("\\", "/").lstrip("/")
new_api_path = new_path.replace("\\", "/").lstrip("/")
full_path = self._get_os_path(new_api_path)
result = self._original_rename_file(old_api_path, new_api_path)
if _is_juvio_file(new_api_path):
with FileLock(full_path):
if os.path.getsize(full_path) == 0:
with open(full_path, "w", encoding="utf-8") as f:
f.write(JuvioConverter.create())
return result
async def rename_file(self, old_path, new_path):
old_api_path = old_path.replace("\\", "/").lstrip("/")
new_api_path = new_path.replace("\\", "/").lstrip("/")
full_path = self._get_os_path(new_api_path)
result = await self._original_rename_file(old_api_path, new_api_path)
async with AsyncFileLock(full_path):
if os.path.getsize(full_path) == 0:
async with aiofiles.open(full_path, "w", encoding="utf-8") as f:
await f.write(JuvioConverter.create())
return result
def create_juvio_contents_manager_class(base_manager_class):
is_async = inspect.iscoroutinefunction(base_manager_class.get)
if is_async:
class AsyncJuvioContentsManager(base_manager_class):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._original_get = super().get
self._original_save = super().save
self._original_rename_file = super().rename_file
async def get(self, path, content=True, type=None, format=None, **kwargs):
return await juvio_get_async(
self, path, content=content, type=type, format=format, **kwargs
)
async def save(self, model, path, **kwargs):
return await juvio_save_async(self, model, path, **kwargs)
async def rename_file(self, old_path, new_path):
return await rename_file(self, old_path, new_path)
return AsyncJuvioContentsManager
else:
class JuvioContentsManager(base_manager_class):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._original_get = super().get
self._original_save = super().save
self._original_rename_file = super().rename_file
def get(self, path, content=True, type=None, format=None, **kwargs):
return juvio_get_sync(
self, path, content=content, type=type, format=format, **kwargs
)
def save(self, model, path, **kwargs):
return juvio_save_sync(self, model, path, **kwargs)
def rename_file(self, old_path, new_path):
return juvio_rename_file(self, old_path, new_path)
return JuvioContentsManager
================================================
FILE: juvio/converter.py
================================================
import sys
import nbformat
if sys.version_info >= (3, 11):
import tomllib
else:
raise ImportError("Python 3.11+ is required for tomllib support.")
class JuvioConverter:
@staticmethod
def _parse_metadata(metadata_lines):
toml_text = "\n".join(metadata_lines)
data = tomllib.loads(toml_text)
if "requires-python" in data:
version = data["requires-python"]
# Clean up operators like >=, ~=, etc.
for prefix in (">=", "~=", "==", ">", "<=", "<"):
if version.startswith(prefix):
version = version[len(prefix) :].strip()
break
data["python_version"] = version
data.pop("requires-python", None)
return data
@staticmethod
def _generate_metadata(python_version, dependencies):
lines = []
lines.append(f'requires-python = "=={python_version}"')
if dependencies:
lines.append("dependencies = [")
for dep in dependencies:
lines.append(f' "{dep}",')
lines.append("]")
else:
lines.append("dependencies = []")
return lines
@staticmethod
def convert_script_to_notebook(text: str):
nb = nbformat.v4.new_notebook()
nb.metadata.kernelspec = {
"name": "juvio",
"language": "python",
"display_name": "Juvio",
}
nb.metadata.language_info = {
"name": "python",
"version": "3.10",
"mimetype": "text/x-python",
"codemirror_mode": {"name": "ipython", "version": 3},
"pygments_lexer": "ipython3",
"nbconvert_exporter": "python",
"file_extension": ".py",
}
lines = text.splitlines()
metadata_lines = []
body_lines = []
in_metadata = False
for line in lines:
if line.startswith("# ///"):
in_metadata = not in_metadata
elif in_metadata:
if line.strip().startswith("#"):
metadata_lines.append(line.strip("# ").rstrip())
else:
body_lines.append(line)
lines = body_lines
cells = []
cell_source = []
cell_type = "code"
for line in lines:
if line.startswith("# %%"):
if cell_source:
if cell_type == "markdown":
cleaned = [
l[2:] if l.startswith("# ") else l.lstrip("#")
for l in cell_source
]
cells.append(nbformat.v4.new_markdown_cell("\n".join(cleaned)))
else:
cells.append(nbformat.v4.new_code_cell("\n".join(cell_source)))
cell_source = []
if "markdown" in line.lower():
cell_type = "markdown"
else:
cell_type = "code"
else:
cell_source.append(line)
if cell_source:
if cell_type == "markdown":
cleaned = [
l[2:] if l.startswith("# ") else l.lstrip("#") for l in cell_source
]
cells.append(nbformat.v4.new_markdown_cell("\n".join(cleaned)))
else:
cells.append(nbformat.v4.new_code_cell("\n".join(cell_source)))
if not cells:
cells.append(nbformat.v4.new_code_cell(""))
nb.cells = cells
return nb
@staticmethod
def convert_notebook_to_script(nb, dep_metadata: list[str] | None = None):
dep_metadata = dep_metadata or []
lines = []
if dep_metadata:
lines.append("# /// script")
for line in dep_metadata:
lines.append("# " + line)
lines.append("# ///")
for cell in nb.cells:
if cell.cell_type == "code":
lines.append("# %%")
lines.append(cell.source.rstrip() if cell.source else "")
elif cell.cell_type == "markdown":
lines.append("# %% markdown")
if cell.source:
for line in cell.source.rstrip().splitlines():
lines.append(
"# " + line if not line.startswith("#") else "# " + line
)
else:
lines.append("")
return "\n".join(lines) + "\n"
@staticmethod
def create(python_version="3.10", dependencies=None):
if dependencies is None:
dependencies = []
lines = []
lines.append("# /// script")
metadata_block = JuvioConverter._generate_metadata(python_version, dependencies)
for line in metadata_block:
lines.append("# " + line)
lines.append("# ///")
lines.append("# %%")
lines.append("")
return "\n".join(lines) + "\n"
@staticmethod
def extract_metadata(text: str):
lines = text.splitlines()
metadata_lines = []
in_metadata = False
for line in lines:
if line.startswith("# ///"):
in_metadata = not in_metadata
elif in_metadata:
if line.strip().startswith("#"):
metadata_lines.append(line.strip("# ").rstrip())
if metadata_lines:
return JuvioConverter._parse_metadata(metadata_lines)
else:
return {
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
"dependencies": [],
}
================================================
FILE: juvio/kernel_launcher.py
================================================
import os
import sys
import platform
from juvio.converter import JuvioConverter
current_path = os.path.dirname(os.path.abspath(__file__))
shared_path = os.path.join(current_path, "shared.py")
def cmd(
python_version=None,
deps=None,
):
head = [
"uv",
"run",
"--no-project",
"--isolated",
"--exact",
"--with",
"ipykernel",
]
head.append("--python")
if python_version is not None:
head.append(python_version)
else:
head.append(f"{sys.version_info.major}.{sys.version_info.minor}")
if deps is not None and len(deps) > 0:
for dep in deps:
head.append("--with")
head.append(f"{dep}")
else:
head.append("-n")
head.extend(["--", "python", shared_path])
head += sys.argv[1:]
return head
def main():
notebook_path = os.environ.get("JPY_SESSION_NAME", None)
if notebook_path is None:
print("No notebook path found in environment variable JPY_SESSION_NAME.")
sys.exit(1)
with open(notebook_path, "r", encoding="utf-8") as f:
text = f.read()
metadata = JuvioConverter.extract_metadata(text)
if platform.system() == "Windows":
import subprocess
proc = subprocess.Popen(
cmd(
python_version=metadata.get("python_version", None),
deps=metadata.get("dependencies", []),
)
)
proc.wait()
sys.exit(proc.returncode)
else:
os.execvp(
"uv",
cmd(
python_version=metadata.get("python_version", None),
deps=metadata.get("dependencies", []),
),
)
================================================
FILE: juvio/kernelspec/kernel.json
================================================
{
"argv": [
"juvio-kernel-launcher",
"-f",
"{connection_file}"
],
"name": "juvio",
"display_name": "Juvio",
"language": "python"
}
================================================
FILE: juvio/labextension/package.json
================================================
{
"name": "juvio_frontend",
"version": "0.1.0",
"description": "Juvio jupyterlab extension.",
"keywords": [
"jupyter",
"jupyterlab",
"jupyterlab-extension"
],
"homepage": "-",
"bugs": {
"url": "-/issues"
},
"license": "BSD-3-Clause",
"author": {
"name": "Oleh Kostromin",
"email": "oleh@beastbyte.ai"
},
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"style": "style/index.css",
"repository": {
"type": "git",
"url": "-.git"
},
"workspaces": [
"ui-tests"
],
"scripts": {
"build": "jlpm build:lib && jlpm build:labextension:dev",
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
"build:labextension": "jupyter labextension build .",
"build:labextension:dev": "jupyter labextension build --development True .",
"build:lib": "tsc --sourceMap",
"build:lib:prod": "tsc",
"clean": "jlpm clean:lib",
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
"clean:labextension": "rimraf juvio_frontend/labextension juvio_frontend/_version.py",
"clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
"eslint": "jlpm eslint:check --fix",
"eslint:check": "eslint . --cache --ext .ts,.tsx",
"install:extension": "jlpm build",
"lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
"lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
"prettier": "jlpm prettier:base --write --list-different",
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"prettier:check": "jlpm prettier:base --check",
"stylelint": "jlpm stylelint:check --fix",
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
"test": "jest --coverage",
"watch": "run-p watch:src watch:labextension",
"watch:src": "tsc -w --sourceMap",
"watch:labextension": "jupyter labextension watch ."
},
"dependencies": {
"@jupyterlab/application": "^4.0.0",
"@jupyterlab/launcher": "^4.0.0"
},
"devDependencies": {
"@jupyterlab/builder": "^4.0.0",
"@jupyterlab/testutils": "^4.0.0",
"@types/jest": "^29.2.0",
"@types/json-schema": "^7.0.11",
"@types/react": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"css-loader": "^6.7.1",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.7.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"rimraf": "^4.4.1",
"source-map-loader": "^1.0.2",
"style-loader": "^3.3.1",
"stylelint": "^14.9.1",
"stylelint-config-prettier": "^9.0.4",
"stylelint-config-recommended": "^8.0.0",
"stylelint-config-standard": "^26.0.0",
"stylelint-prettier": "^2.0.0",
"typescript": "^5.0",
"yjs": "^13.5.0"
},
"sideEffects": [
"style/*.css",
"style/index.js"
],
"styleModule": "style/index.js",
"publishConfig": {
"access": "public"
},
"jupyterlab": {
"extension": true,
"outputDir": "build/labextension",
"_build": {
"load": "static/remoteEntry.cb75fcc929fa00484f36.js",
"extension": "./extension",
"style": "./style"
}
},
"eslintIgnore": [
"node_modules",
"dist",
"coverage",
"**/*.d.ts",
"tests",
"**/__tests__",
"ui-tests"
],
"eslintConfig": {
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": [
"PascalCase"
],
"custom": {
"regex": "^I[A-Z]",
"match": true
}
}
],
"@typescript-eslint/no-unused-vars": [
"warn",
{
"args": "none"
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": false
}
],
"curly": [
"error",
"all"
],
"eqeqeq": "error",
"prefer-arrow-callback": "error"
}
},
"prettier": {
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "avoid",
"endOfLine": "auto"
},
"stylelint": {
"extends": [
"stylelint-config-recommended",
"stylelint-config-standard",
"stylelint-prettier/recommended"
],
"rules": {
"property-no-vendor-prefix": null,
"selector-no-vendor-prefix": null,
"value-no-vendor-prefix": null
}
}
}
================================================
FILE: juvio/labextension/static/509.8b21d569ebbb8f039bdd.js
================================================
"use strict";(self.webpackChunkjuvio_frontend=self.webpackChunkjuvio_frontend||[]).push([[509],{509:(e,o,t)=>{t.r(o),t.d(o,{default:()=>n});const n={id:"juvio_frontend:plugin",description:"Juvio JupyterLab extension",autoStart:!0,requires:[t(625).ILauncher],activate:(e,o)=>{console.log("JupyterLab extension juvio_frontend is activated!"),e.docRegistry.addFileType({name:"juvio",displayName:"Juvio Notebook",extensions:[".juvio"],contentType:"notebook",fileFormat:"json"}),e.docRegistry.addFileType({name:"notebook",extensions:[".juvio"]});const{commands:t}=e;t.addCommand("create-juvio-file",{label:"Juvio Notebook",execute:()=>t.execute("docmanager:new-untitled",{type:"file",ext:".juvio",kernelName:"juvio"})}),o.add({category:"Notebook",rank:1,command:"create-juvio-file",kernelIconUrl:"https://iili.io/3WtCOjs.th.png"})}}}}]);
================================================
FILE: juvio/labextension/static/728.864a8d7732d3e14d284a.js
================================================
"use strict";(self.webpackChunkjuvio_frontend=self.webpackChunkjuvio_frontend||[]).push([[728],{56:(e,t,n)=>{e.exports=function(e){var t=n.nc;t&&e.setAttribute("nonce",t)}},72:e=>{var t=[];function n(e){for(var n=-1,r=0;r<t.length;r++)if(t[r].identifier===e){n=r;break}return n}function r(e,r){for(var a={},i=[],s=0;s<e.length;s++){var c=e[s],u=r.base?c[0]+r.base:c[0],l=a[u]||0,p="".concat(u," ").concat(l);a[u]=l+1;var f=n(p),d={css:c[1],media:c[2],sourceMap:c[3],supports:c[4],layer:c[5]};if(-1!==f)t[f].references++,t[f].updater(d);else{var v=o(d,r);r.byIndex=s,t.splice(s,0,{identifier:p,updater:v,references:1})}i.push(p)}return i}function o(e,t){var n=t.domAPI(t);return n.update(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap&&t.supports===e.supports&&t.layer===e.layer)return;n.update(e=t)}else n.remove()}}e.exports=function(e,o){var a=r(e=e||[],o=o||{});return function(e){e=e||[];for(var i=0;i<a.length;i++){var s=n(a[i]);t[s].references--}for(var c=r(e,o),u=0;u<a.length;u++){var l=n(a[u]);0===t[l].references&&(t[l].updater(),t.splice(l,1))}a=c}}},113:e=>{e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}},314:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n="",r=void 0!==t[5];return t[4]&&(n+="@supports (".concat(t[4],") {")),t[2]&&(n+="@media ".concat(t[2]," {")),r&&(n+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),n+=e(t),r&&(n+="}"),t[2]&&(n+="}"),t[4]&&(n+="}"),n})).join("")},t.i=function(e,n,r,o,a){"string"==typeof e&&(e=[[null,e,void 0]]);var i={};if(r)for(var s=0;s<this.length;s++){var c=this[s][0];null!=c&&(i[c]=!0)}for(var u=0;u<e.length;u++){var l=[].concat(e[u]);r&&i[l[0]]||(void 0!==a&&(void 0===l[5]||(l[1]="@layer".concat(l[5].length>0?" ".concat(l[5]):""," {").concat(l[1],"}")),l[5]=a),n&&(l[2]?(l[1]="@media ".concat(l[2]," {").concat(l[1],"}"),l[2]=n):l[2]=n),o&&(l[4]?(l[1]="@supports (".concat(l[4],") {").concat(l[1],"}"),l[4]=o):l[4]="".concat(o)),t.push(l))}},t}},475:(e,t,n)=>{n.d(t,{A:()=>s});var r=n(601),o=n.n(r),a=n(314),i=n.n(a)()(o());i.push([e.id,"/*\n See the JupyterLab Developer Guide for useful CSS Patterns:\n\n https://jupyterlab.readthedocs.io/en/stable/developer/css.html\n*/\n",""]);const s=i},540:e=>{e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},601:e=>{e.exports=function(e){return e[1]}},659:e=>{var t={};e.exports=function(e,n){var r=function(e){if(void 0===t[e]){var n=document.querySelector(e);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}t[e]=n}return t[e]}(e);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(n)}},728:(e,t,n)=>{var r=n(72),o=n.n(r),a=n(825),i=n.n(a),s=n(659),c=n.n(s),u=n(56),l=n.n(u),p=n(540),f=n.n(p),d=n(113),v=n.n(d),h=n(475),m={};m.styleTagTransform=v(),m.setAttributes=l(),m.insert=c().bind(null,"head"),m.domAPI=i(),m.insertStyleElement=f(),o()(h.A,m),h.A&&h.A.locals&&h.A.locals},825:e=>{e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var t=e.insertStyleElement(e);return{update:function(n){!function(e,t,n){var r="";n.supports&&(r+="@supports (".concat(n.supports,") {")),n.media&&(r+="@media ".concat(n.media," {"));var o=void 0!==n.layer;o&&(r+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),r+=n.css,o&&(r+="}"),n.media&&(r+="}"),n.supports&&(r+="}");var a=n.sourceMap;a&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),t.styleTagTransform(r,e,t.options)}(t,e,n)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}}}]);
================================================
FILE: juvio/labextension/static/remoteEntry.cb75fcc929fa00484f36.js
================================================
var _JUPYTERLAB;(()=>{"use strict";var e,r,t,n,o,i,a,u,f,l,s,d,p,c,h,v,g,b,m,y={678:(e,r,t)=>{var n={"./index":()=>t.e(509).then((()=>()=>t(509))),"./extension":()=>t.e(509).then((()=>()=>t(509))),"./style":()=>t.e(728).then((()=>()=>t(728)))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then((()=>{throw new Error('Module "'+e+'" does not exist in container.')})),t.R=void 0,r),i=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>i})}},w={};function S(e){var r=w[e];if(void 0!==r)return r.exports;var t=w[e]={id:e,exports:{}};return y[e](t,t.exports,S),t.exports}S.m=y,S.c=w,S.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return S.d(r,{a:r}),r},S.d=(e,r)=>{for(var t in r)S.o(r,t)&&!S.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},S.f={},S.e=e=>Promise.all(Object.keys(S.f).reduce(((r,t)=>(S.f[t](e,r),r)),[])),S.u=e=>e+"."+{509:"8b21d569ebbb8f039bdd",728:"864a8d7732d3e14d284a"}[e]+".js?v="+{509:"8b21d569ebbb8f039bdd",728:"864a8d7732d3e14d284a"}[e],S.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),S.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="juvio_frontend:",S.l=(t,n,o,i)=>{if(e[t])e[t].push(n);else{var a,u;if(void 0!==o)for(var f=document.getElementsByTagName("script"),l=0;l<f.length;l++){var s=f[l];if(s.getAttribute("src")==t||s.getAttribute("data-webpack")==r+o){a=s;break}}a||(u=!0,(a=document.createElement("script")).charset="utf-8",a.timeout=120,S.nc&&a.setAttribute("nonce",S.nc),a.setAttribute("data-webpack",r+o),a.src=t),e[t]=[n];var d=(r,n)=>{a.onerror=a.onload=null,clearTimeout(p);var o=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),o&&o.forEach((e=>e(n))),r)return r(n)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=d.bind(null,a.onerror),a.onload=d.bind(null,a.onload),u&&document.head.appendChild(a)}},S.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{S.S={};var e={},r={};S.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];S.o(S.S,t)||(S.S[t]={});var i=S.S[t],a="juvio_frontend",u=[];return"default"===t&&((e,r,t,n)=>{var o=i[e]=i[e]||{},u=o[r];(!u||!u.loaded&&(1!=!u.eager?n:a>u.from))&&(o[r]={get:()=>S.e(509).then((()=>()=>S(509))),from:a,eager:!1})})("juvio_frontend","0.1.0"),e[t]=u.length?Promise.all(u).then((()=>e[t]=1)):1}}})(),(()=>{var e;S.g.importScripts&&(e=S.g.location+"");var r=S.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),S.p=e})(),t=e=>{var r=e=>e.split(".").map((e=>+e==e?+e:e)),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},n=(e,r)=>{e=t(e),r=t(r);for(var n=0;;){if(n>=e.length)return n<r.length&&"u"!=(typeof r[n])[0];var o=e[n],i=(typeof o)[0];if(n>=r.length)return"u"==i;var a=r[n],u=(typeof a)[0];if(i!=u)return"o"==i&&"n"==u||"s"==u||"u"==i;if("o"!=i&&"u"!=i&&o!=a)return o<a;n++}},o=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,i=1;i<e.length;i++)n--,t+="u"==(typeof(u=e[i]))[0]?"-":(n>0?".":"")+(n=2,u);return t}var a=[];for(i=1;i<e.length;i++){var u=e[i];a.push(0===u?"not("+f()+")":1===u?"("+f()+" || "+f()+")":2===u?a.pop()+" "+a.pop():o(u))}return f();function f(){return a.pop().replace(/^\((.+)\)$/,"$1")}},i=(e,r)=>{if(0 in e){r=t(r);var n=e[0],o=n<0;o&&(n=-n-1);for(var a=0,u=1,f=!0;;u++,a++){var l,s,d=u<e.length?(typeof e[u])[0]:"";if(a>=r.length||"o"==(s=(typeof(l=r[a]))[0]))return!f||("u"==d?u>n&&!o:""==d!=o);if("u"==s){if(!f||"u"!=d)return!1}else if(f)if(d==s)if(u<=n){if(l!=e[u])return!1}else{if(o?l>e[u]:l<e[u])return!1;l!=e[u]&&(f=!1)}else if("s"!=d&&"n"!=d){if(o||u<=n)return!1;f=!1,u--}else{if(u<=n||s<d!=o)return!1;f=!1}else"s"!=d&&"n"!=d&&(f=!1,u--)}}var p=[],c=p.pop.bind(p);for(a=1;a<e.length;a++){var h=e[a];p.push(1==h?c()|c():2==h?c()&c():h?i(h,r):!c())}return!!c()},a=(e,r)=>e&&S.o(e,r),u=e=>(e.loaded=1,e.get()),f=e=>Object.keys(e).reduce(((r,t)=>(e[t].eager&&(r[t]=e[t]),r)),{}),l=(e,r,t)=>{var o=t?f(e[r]):e[r];return Object.keys(o).reduce(((e,r)=>!e||!o[e].loaded&&n(e,r)?r:e),0)},s=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+o(n)+")",d=e=>{throw new Error(e)},p=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},c=(e,r,t)=>t?t():((e,r)=>d("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),h=(e=>function(r,t,n,o,i){var a=S.I(r);return a&&a.then&&!n?a.then(e.bind(e,r,S.S[r],t,!1,o,i)):e(r,S.S[r],t,n,o,i)})(((e,r,t,n,o,f)=>{if(!a(r,t))return c(e,t,f);var d=l(r,t,n);return i(o,d)||p(s(r,t,d,o)),u(r[t][d])})),v={},g={625:()=>h("default","@jupyterlab/launcher",!1,[1,4,4,1])},b={509:[625]},m={},S.f.consumes=(e,r)=>{S.o(b,e)&&b[e].forEach((e=>{if(S.o(v,e))return r.push(v[e]);if(!m[e]){var t=r=>{v[e]=0,S.m[e]=t=>{delete S.c[e],t.exports=r()}};m[e]=!0;var n=r=>{delete v[e],S.m[e]=t=>{throw delete S.c[e],r}};try{var o=g[e]();o.then?r.push(v[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}}))},(()=>{var e={561:0};S.f.j=(r,t)=>{var n=S.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var i=S.p+S.u(r),a=new Error;S.l(i,(t=>{if(S.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;a.message="Loading chunk "+r+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,n[1](a)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[i,a,u]=t,f=0;if(i.some((r=>0!==e[r]))){for(n in a)S.o(a,n)&&(S.m[n]=a[n]);u&&u(S)}for(r&&r(t);f<i.length;f++)o=i[f],S.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=self.webpackChunkjuvio_frontend=self.webpackChunkjuvio_frontend||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),S.nc=void 0;var j=S(678);(_JUPYTERLAB=void 0===_JUPYTERLAB?{}:_JUPYTERLAB).juvio_frontend=j})();
================================================
FILE: juvio/labextension/static/style.js
================================================
/* This is a generated file of CSS imports */
/* It was generated by @jupyterlab/builder in Build.ensureAssets() */
import 'juvio_frontend/style/index.js';
================================================
FILE: juvio/labextension/static/third-party-licenses.json
================================================
{
"packages": [
{
"name": "css-loader",
"versionInfo": "6.11.0",
"licenseId": "MIT",
"extractedText": "Copyright JS Foundation and other contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
},
{
"name": "style-loader",
"versionInfo": "3.3.4",
"licenseId": "MIT",
"extractedText": "Copyright JS Foundation and other contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
}
]
}
================================================
FILE: juvio/server_extension.py
================================================
def _load_jupyter_server_extension(app):
app.log.info("[juvio] Loading Juvio")
from juvio.content_manager import create_juvio_contents_manager_class
original_contents_manager = app.contents_manager_class
new_cm_class = create_juvio_contents_manager_class(original_contents_manager)
app.contents_manager_class = new_cm_class
app.contents_manager = new_cm_class(parent=app, log=app.log)
app.session_manager.contents_manager = app.contents_manager
app.web_app.settings["contents_manager"] = app.contents_manager
================================================
FILE: juvio/shared.py
================================================
from ipykernel import kernelapp as app_module
import time
import platform
import asyncio
import hashlib
import os
import tempfile
if platform.system() == "Windows":
import msvcrt
else:
import fcntl
class FileLock:
def __init__(self, filepath, timeout=10, delay=0.05):
self.filepath = filepath
self.timeout = timeout
self.delay = delay
if platform.system() == "Windows":
abs_path = os.path.abspath(filepath)
h = hashlib.sha256(abs_path.encode("utf-8")).hexdigest()[:16]
lock_filename = f"{os.path.basename(filepath)}.{h}.lock"
self.lockfile_path = os.path.join(tempfile.gettempdir(), lock_filename)
else:
self.lockfile_path = filepath
self.file = None
def acquire(self):
start_time = time.time()
if platform.system() == "Windows":
while True:
try:
self.file = os.open(
self.lockfile_path, os.O_CREAT | os.O_EXCL | os.O_RDWR
)
break # acquired lock
except FileExistsError:
if (time.time() - start_time) >= self.timeout:
raise TimeoutError(
f"Timeout acquiring lock for {self.filepath}"
)
time.sleep(self.delay)
else:
self.file = open(self.lockfile_path, "a+")
while True:
try:
import fcntl
fcntl.flock(self.file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except BlockingIOError:
if (time.time() - start_time) >= self.timeout:
self.file.close()
raise TimeoutError(
f"Timeout acquiring lock for {self.filepath}"
)
time.sleep(self.delay)
def release(self):
if self.file:
if platform.system() == "Windows":
os.close(self.file)
os.unlink(self.lockfile_path)
else:
import fcntl
fcntl.flock(self.file.fileno(), fcntl.LOCK_UN)
self.file.close()
def __enter__(self):
self.acquire()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.release()
class AsyncFileLock:
def __init__(self, filepath, timeout=10, delay=0.05):
self.filepath = filepath
self.timeout = timeout
self.delay = delay
if platform.system() == "Windows":
abs_path = os.path.abspath(filepath)
h = hashlib.sha256(abs_path.encode("utf-8")).hexdigest()[:16]
lock_filename = f"{os.path.basename(filepath)}.{h}.lock"
self.lockfile_path = os.path.join(tempfile.gettempdir(), lock_filename)
else:
self.lockfile_path = filepath
self.file = None
async def acquire(self):
start_time = time.time()
if platform.system() == "Windows":
while True:
try:
self.file = os.open(
self.lockfile_path, os.O_CREAT | os.O_EXCL | os.O_RDWR
)
break
except FileExistsError:
if (time.time() - start_time) >= self.timeout:
raise TimeoutError(
f"Timeout acquiring lock for {self.filepath}"
)
await asyncio.sleep(self.delay)
else:
self.file = open(self.lockfile_path, "a+")
while True:
try:
fcntl.flock(self.file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except BlockingIOError:
if (time.time() - start_time) >= self.timeout:
self.file.close()
raise TimeoutError(
f"Timeout acquiring lock for {self.filepath}"
)
await asyncio.sleep(self.delay)
async def release(self):
if self.file:
if platform.system() == "Windows":
os.close(self.file)
os.unlink(self.lockfile_path)
else:
fcntl.flock(self.file.fileno(), fcntl.LOCK_UN)
self.file.close()
async def __aenter__(self):
await self.acquire()
return self
async def __aexit__(self, exc_type, exc_value, traceback):
await self.release()
def load_ipython_extension(ip):
def juvio(line):
import subprocess, os, sys
command = ["uv", "pip"] + line.split()
try:
subprocess.check_call(command)
except subprocess.CalledProcessError as e:
print(f"Error: {e}")
original_no_color = os.environ.get("NO_COLOR", "1")
os.environ["NO_COLOR"] = "1"
try:
result = subprocess.run(
["uv", "pip", "freeze", "--no-color"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
text=True,
check=True,
)
deps = result.stdout.split("\n")
except subprocess.CalledProcessError as e:
print(f"Error: {e}")
print("Stdout:", e.stdout)
print("Stderr:", e.stderr)
finally:
os.environ["NO_COLOR"] = original_no_color
notebook_path = os.environ.get("JPY_SESSION_NAME", None)
if notebook_path is None:
raise ValueError(
"No notebook path found in environment variable JPY_SESSION_NAME."
)
with FileLock(notebook_path):
with open(notebook_path, "r+", encoding="utf-8") as f:
content = f.read()
blocks = content.split("# ///")
cells = blocks[2:]
pyver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
lines = [
"# /// script",
f'# requires-python = "=={pyver}"',
"# dependencies = [",
]
for d in deps:
if len(d) > 0:
lines.append(f'# "{d}",')
lines.append("# ]\n")
f.seek(0)
metadata = "\n".join(lines)
new = "# ///".join([metadata] + cells)
f.write(new)
f.truncate()
ip.register_magic_function(juvio, "line")
def start():
kernel_app = app_module.IPKernelApp.instance()
kernel_app.initialize()
load_ipython_extension(kernel_app.shell)
kernel_app.start()
if __name__ == "__main__":
start()
================================================
FILE: juvio_frontend/.gitignore
================================================
*.bundle.*
lib/
node_modules/
*.log
.eslintcache
.stylelintcache
*.egg-info/
.ipynb_checkpoints
*.tsbuildinfo
juvio_frontend/labextension
# Version file is handled by hatchling
juvio_frontend/_version.py
# Integration tests
ui-tests/test-results/
ui-tests/playwright-report/
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage/
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# End of https://www.gitignore.io/api/python
# OSX files
.DS_Store
# Yarn cache
.yarn/
================================================
FILE: juvio_frontend/.prettierignore
================================================
node_modules
**/node_modules
**/lib
**/package.json
!/package.json
juvio_frontend
================================================
FILE: juvio_frontend/.yarnrc.yml
================================================
enableImmutableInstalls: false
nodeLinker: node-modules
================================================
FILE: juvio_frontend/CHANGELOG.md
================================================
# Changelog
<!-- <START NEW CHANGELOG ENTRY> -->
<!-- <END NEW CHANGELOG ENTRY> -->
================================================
FILE: juvio_frontend/LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2025, Oleh Kostromin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: juvio_frontend/README.md
================================================
# juvio_frontend
[](-/actions/workflows/build.yml)
Juvio jupyterlab extension.
## Requirements
- JupyterLab >= 4.0.0
## Install
To install the extension, execute:
```bash
pip install juvio_frontend
```
## Uninstall
To remove the extension, execute:
```bash
pip uninstall juvio_frontend
```
## Contributing
### Development install
Note: You will need NodeJS to build the extension package.
The `jlpm` command is JupyterLab's pinned version of
[yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use
`yarn` or `npm` in lieu of `jlpm` below.
```bash
# Clone the repo to your local environment
# Change directory to the juvio_frontend directory
# Install package in development mode
pip install -e "."
# Link your development version of the extension with JupyterLab
jupyter labextension develop . --overwrite
# Rebuild extension Typescript source after making changes
jlpm build
```
You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension.
```bash
# Watch the source directory in one terminal, automatically rebuilding when needed
jlpm watch
# Run JupyterLab in another terminal
jupyter lab
```
With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt).
By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command:
```bash
jupyter lab build --minimize=False
```
### Development uninstall
```bash
pip uninstall juvio_frontend
```
In development mode, you will also need to remove the symlink created by `jupyter labextension develop`
command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions`
folder is located. Then you can remove the symlink named `juvio_frontend` within that folder.
### Testing the extension
#### Frontend tests
This extension is using [Jest](https://jestjs.io/) for JavaScript code testing.
To execute them, execute:
```sh
jlpm
jlpm test
```
#### Integration tests
This extension uses [Playwright](https://playwright.dev/docs/intro/) for the integration tests (aka user level tests).
More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab.
More information are provided within the [ui-tests](./ui-tests/README.md) README.
### Packaging the extension
See [RELEASE](RELEASE.md)
================================================
FILE: juvio_frontend/RELEASE.md
================================================
# Making a new release of juvio_frontend
The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser).
## Manual release
### Python package
This extension can be distributed as Python packages. All of the Python
packaging instructions are in the `pyproject.toml` file to wrap your extension in a
Python package. Before generating a package, you first need to install some tools:
```bash
pip install build twine hatch
```
Bump the version using `hatch`. By default this will create a tag.
See the docs on [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version#semver) for details.
```bash
hatch version <new-version>
```
Make sure to clean up all the development files before building the package:
```bash
jlpm clean:all
```
You could also clean up the local git repository:
```bash
git clean -dfX
```
To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do:
```bash
python -m build
```
> `python setup.py sdist bdist_wheel` is deprecated and will not work for this package.
Then to upload the package to PyPI, do:
```bash
twine upload dist/*
```
### NPM package
To publish the frontend part of the extension as a NPM package, do:
```bash
npm login
npm publish --access public
```
## Automated releases with the Jupyter Releaser
The extension repository should already be compatible with the Jupyter Releaser.
Check out the [workflow documentation](https://jupyter-releaser.readthedocs.io/en/latest/get_started/making_release_from_repo.html) for more information.
Here is a summary of the steps to cut a new release:
- Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the [Github Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) in the repository
- Go to the Actions panel
- Run the "Step 1: Prep Release" workflow
- Check the draft changelog
- Run the "Step 2: Publish Release" workflow
## Publishing to `conda-forge`
If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html
Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically.
================================================
FILE: juvio_frontend/babel.config.js
================================================
module.exports = require('@jupyterlab/testutils/lib/babel.config');
================================================
FILE: juvio_frontend/install.json
================================================
{
"packageManager": "python",
"packageName": "juvio_frontend",
"uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package juvio_frontend"
}
================================================
FILE: juvio_frontend/jest.config.js
================================================
const jestJupyterLab = require('@jupyterlab/testutils/lib/jest-config');
const esModules = [
'@codemirror',
'@jupyter/ydoc',
'@jupyterlab/',
'lib0',
'nanoid',
'vscode-ws-jsonrpc',
'y-protocols',
'y-websocket',
'yjs'
].join('|');
const baseConfig = jestJupyterLab(__dirname);
module.exports = {
...baseConfig,
automock: false,
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/.ipynb_checkpoints/*'
],
coverageReporters: ['lcov', 'text'],
testRegex: 'src/.*/.*.spec.ts[x]?$',
transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`]
};
================================================
FILE: juvio_frontend/package.json
================================================
{
"name": "juvio_frontend",
"version": "0.1.0",
"description": "Juvio jupyterlab extension.",
"keywords": [
"jupyter",
"jupyterlab",
"jupyterlab-extension"
],
"homepage": "-",
"bugs": {
"url": "-/issues"
},
"license": "BSD-3-Clause",
"author": {
"name": "Oleh Kostromin",
"email": "oleh@beastbyte.ai"
},
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"style": "style/index.css",
"repository": {
"type": "git",
"url": "-.git"
},
"workspaces": [
"ui-tests"
],
"scripts": {
"build": "jlpm build:lib && jlpm build:labextension:dev",
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
"build:labextension": "jupyter labextension build .",
"build:labextension:dev": "jupyter labextension build --development True .",
"build:lib": "tsc --sourceMap",
"build:lib:prod": "tsc",
"clean": "jlpm clean:lib",
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
"clean:labextension": "rimraf juvio_frontend/labextension juvio_frontend/_version.py",
"clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
"eslint": "jlpm eslint:check --fix",
"eslint:check": "eslint . --cache --ext .ts,.tsx",
"install:extension": "jlpm build",
"lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
"lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
"prettier": "jlpm prettier:base --write --list-different",
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"prettier:check": "jlpm prettier:base --check",
"stylelint": "jlpm stylelint:check --fix",
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
"test": "jest --coverage",
"watch": "run-p watch:src watch:labextension",
"watch:src": "tsc -w --sourceMap",
"watch:labextension": "jupyter labextension watch ."
},
"dependencies": {
"@jupyterlab/application": "^4.0.0",
"@jupyterlab/launcher": "^4.0.0"
},
"devDependencies": {
"@jupyterlab/builder": "^4.0.0",
"@jupyterlab/testutils": "^4.0.0",
"@types/jest": "^29.2.0",
"@types/json-schema": "^7.0.11",
"@types/react": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"css-loader": "^6.7.1",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.7.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"rimraf": "^4.4.1",
"source-map-loader": "^1.0.2",
"style-loader": "^3.3.1",
"stylelint": "^14.9.1",
"stylelint-config-prettier": "^9.0.4",
"stylelint-config-recommended": "^8.0.0",
"stylelint-config-standard": "^26.0.0",
"stylelint-prettier": "^2.0.0",
"typescript": "^5.0",
"yjs": "^13.5.0"
},
"sideEffects": [
"style/*.css",
"style/index.js"
],
"styleModule": "style/index.js",
"publishConfig": {
"access": "public"
},
"jupyterlab": {
"extension": true,
"outputDir": "build/labextension"
},
"eslintIgnore": [
"node_modules",
"dist",
"coverage",
"**/*.d.ts",
"tests",
"**/__tests__",
"ui-tests"
],
"eslintConfig": {
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": [
"PascalCase"
],
"custom": {
"regex": "^I[A-Z]",
"match": true
}
}
],
"@typescript-eslint/no-unused-vars": [
"warn",
{
"args": "none"
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": false
}
],
"curly": [
"error",
"all"
],
"eqeqeq": "error",
"prefer-arrow-callback": "error"
}
},
"prettier": {
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "avoid",
"endOfLine": "auto"
},
"stylelint": {
"extends": [
"stylelint-config-recommended",
"stylelint-config-standard",
"stylelint-prettier/recommended"
],
"rules": {
"property-no-vendor-prefix": null,
"selector-no-vendor-prefix": null,
"value-no-vendor-prefix": null
}
}
}
================================================
FILE: juvio_frontend/pyproject.toml
================================================
[build-system]
requires = ["hatchling>=1.5.0", "jupyterlab>=4.0.0,<5", "hatch-nodejs-version"]
build-backend = "hatchling.build"
[project]
name = "juvio_frontend"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.8"
classifiers = [
"Framework :: Jupyter",
"Framework :: Jupyter :: JupyterLab",
"Framework :: Jupyter :: JupyterLab :: 4",
"Framework :: Jupyter :: JupyterLab :: Extensions",
"Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
dependencies = [
]
dynamic = ["version", "description", "authors", "urls", "keywords"]
[tool.hatch.version]
source = "nodejs"
[tool.hatch.metadata.hooks.nodejs]
fields = ["description", "authors", "urls"]
[tool.hatch.build.targets.sdist]
artifacts = ["juvio_frontend/labextension"]
exclude = [".github", "binder"]
[tool.hatch.build.targets.wheel.shared-data]
"juvio_frontend/labextension" = "share/jupyter/labextensions/juvio_frontend"
"install.json" = "share/jupyter/labextensions/juvio_frontend/install.json"
[tool.hatch.build.hooks.version]
path = "juvio_frontend/_version.py"
[tool.hatch.build.hooks.jupyter-builder]
dependencies = ["hatch-jupyter-builder>=0.5"]
build-function = "hatch_jupyter_builder.npm_builder"
ensured-targets = [
"juvio_frontend/labextension/static/style.js",
"juvio_frontend/labextension/package.json",
]
skip-if-exists = ["juvio_frontend/labextension/static/style.js"]
[tool.hatch.build.hooks.jupyter-builder.build-kwargs]
build_cmd = "build:prod"
npm = ["jlpm"]
[tool.hatch.build.hooks.jupyter-builder.editable-build-kwargs]
build_cmd = "install:extension"
npm = ["jlpm"]
source_dir = "src"
build_dir = "juvio_frontend/labextension"
[tool.jupyter-releaser.options]
version_cmd = "hatch version"
[tool.jupyter-releaser.hooks]
before-build-npm = [
"python -m pip install 'jupyterlab>=4.0.0,<5'",
"jlpm",
"jlpm build:prod"
]
before-build-python = ["jlpm clean:all"]
[tool.check-wheel-contents]
ignore = ["W002"]
================================================
FILE: juvio_frontend/setup.py
================================================
__import__('setuptools').setup()
================================================
FILE: juvio_frontend/src/__tests__/juvio_frontend.spec.ts
================================================
/**
* Example of [Jest](https://jestjs.io/docs/getting-started) unit tests
*/
describe('juvio_frontend', () => {
it('should be tested', () => {
expect(1 + 1).toEqual(2);
});
});
================================================
FILE: juvio_frontend/src/index.ts
================================================
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ILauncher } from '@jupyterlab/launcher';
const plugin: JupyterFrontEndPlugin<void> = {
id: 'juvio_frontend:plugin',
description: 'Juvio JupyterLab extension',
autoStart: true,
requires: [ILauncher],
activate: (
app: JupyterFrontEnd,
launcher: ILauncher
) => {
console.log('JupyterLab extension juvio_frontend is activated!');
app.docRegistry.addFileType({
name: 'juvio',
displayName: 'Juvio Notebook',
extensions: ['.juvio'],
contentType: 'notebook',
fileFormat: 'json',
});
app.docRegistry.addFileType({
name: 'notebook',
extensions: ['.juvio']
});
const { commands } = app;
commands.addCommand('create-juvio-file', {
label: 'Juvio Notebook',
execute: () => {
return commands.execute('docmanager:new-untitled', {
type: 'file',
ext: '.juvio',
kernelName: "juvio",
});
}
});
launcher.add({
category: 'Notebook',
rank: 1,
command: 'create-juvio-file',
kernelIconUrl: 'https://gist.githubusercontent.com/OKUA1/d6e65e883546021ea774857878fd0537/raw/4de2ea217e25d9ff7b3d2a73899e85665ed7d94c/juvio_logo.svg',
});
}
};
export default plugin;
================================================
FILE: juvio_frontend/style/base.css
================================================
/*
See the JupyterLab Developer Guide for useful CSS Patterns:
https://jupyterlab.readthedocs.io/en/stable/developer/css.html
*/
================================================
FILE: juvio_frontend/style/index.css
================================================
@import 'base.css';
================================================
FILE: juvio_frontend/style/index.js
================================================
import './base.css';
================================================
FILE: juvio_frontend/tsconfig.json
================================================
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"composite": true,
"declaration": true,
"esModuleInterop": true,
"incremental": true,
"jsx": "react",
"module": "esnext",
"moduleResolution": "node",
"noEmitOnError": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"preserveWatchOutput": true,
"resolveJsonModule": true,
"outDir": "lib",
"rootDir": "src",
"strict": true,
"strictNullChecks": true,
"target": "ES2018",
"types": ["jest"],
"lib": ["ES2020", "DOM", "DOM.Iterable", "ESNext.Intl"],
},
"include": ["src/*"]
}
================================================
FILE: juvio_frontend/tsconfig.test.json
================================================
{
"extends": "./tsconfig"
}
================================================
FILE: juvio_frontend/ui-tests/README.md
================================================
# Integration Testing
This folder contains the integration tests of the extension.
They are defined using [Playwright](https://playwright.dev/docs/intro) test runner
and [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) helper.
The Playwright configuration is defined in [playwright.config.js](./playwright.config.js).
The JupyterLab server configuration to use for the integration test is defined
in [jupyter_server_test_config.py](./jupyter_server_test_config.py).
The default configuration will produce video for failing tests and an HTML report.
> There is a new experimental UI mode that you may fall in love with; see [that video](https://www.youtube.com/watch?v=jF0yA-JLQW0).
## Run the tests
> All commands are assumed to be executed from the root directory
To run the tests, you need to:
1. Compile the extension:
```sh
jlpm install
jlpm build:prod
```
> Check the extension is installed in JupyterLab.
2. Install test dependencies (needed only once):
```sh
cd ./ui-tests
jlpm install
jlpm playwright install
cd ..
```
3. Execute the [Playwright](https://playwright.dev/docs/intro) tests:
```sh
cd ./ui-tests
jlpm playwright test
```
Test results will be shown in the terminal. In case of any test failures, the test report
will be opened in your browser at the end of the tests execution; see
[Playwright documentation](https://playwright.dev/docs/test-reporters#html-reporter)
for configuring that behavior.
## Update the tests snapshots
> All commands are assumed to be executed from the root directory
If you are comparing snapshots to validate your tests, you may need to update
the reference snapshots stored in the repository. To do that, you need to:
1. Compile the extension:
```sh
jlpm install
jlpm build:prod
```
> Check the extension is installed in JupyterLab.
2. Install test dependencies (needed only once):
```sh
cd ./ui-tests
jlpm install
jlpm playwright install
cd ..
```
3. Execute the [Playwright](https://playwright.dev/docs/intro) command:
```sh
cd ./ui-tests
jlpm playwright test -u
```
> Some discrepancy may occurs between the snapshots generated on your computer and
> the one generated on the CI. To ease updating the snapshots on a PR, you can
> type `please update playwright snapshots` to trigger the update by a bot on the CI.
> Once the bot has computed new snapshots, it will commit them to the PR branch.
## Create tests
> All commands are assumed to be executed from the root directory
To create tests, the easiest way is to use the code generator tool of playwright:
1. Compile the extension:
```sh
jlpm install
jlpm build:prod
```
> Check the extension is installed in JupyterLab.
2. Install test dependencies (needed only once):
```sh
cd ./ui-tests
jlpm install
jlpm playwright install
cd ..
```
3. Start the server:
```sh
cd ./ui-tests
jlpm start
```
4. Execute the [Playwright code generator](https://playwright.dev/docs/codegen) in **another terminal**:
```sh
cd ./ui-tests
jlpm playwright codegen localhost:8888
```
## Debug tests
> All commands are assumed to be executed from the root directory
To debug tests, a good way is to use the inspector tool of playwright:
1. Compile the extension:
```sh
jlpm install
jlpm build:prod
```
> Check the extension is installed in JupyterLab.
2. Install test dependencies (needed only once):
```sh
cd ./ui-tests
jlpm install
jlpm playwright install
cd ..
```
3. Execute the Playwright tests in [debug mode](https://playwright.dev/docs/debug):
```sh
cd ./ui-tests
jlpm playwright test --debug
```
## Upgrade Playwright and the browsers
To update the web browser versions, you must update the package `@playwright/test`:
```sh
cd ./ui-tests
jlpm up "@playwright/test"
jlpm playwright install
```
================================================
FILE: juvio_frontend/ui-tests/jupyter_server_test_config.py
================================================
"""Server configuration for integration tests.
!! Never use this configuration in production because it
opens the server to the world and provide access to JupyterLab
JavaScript objects through the global window variable.
"""
from jupyterlab.galata import configure_jupyter_server
configure_jupyter_server(c)
# Uncomment to set server log level to debug level
# c.ServerApp.log_level = "DEBUG"
================================================
FILE: juvio_frontend/ui-tests/package.json
================================================
{
"name": "juvio_frontend-ui-tests",
"version": "1.0.0",
"description": "JupyterLab juvio_frontend Integration Tests",
"private": true,
"scripts": {
"start": "jupyter lab --config jupyter_server_test_config.py",
"test": "jlpm playwright test",
"test:update": "jlpm playwright test --update-snapshots"
},
"devDependencies": {
"@jupyterlab/galata": "^5.0.0",
"@playwright/test": "^1.32.0"
}
}
================================================
FILE: juvio_frontend/ui-tests/playwright.config.js
================================================
/**
* Configuration for Playwright using default from @jupyterlab/galata
*/
const baseConfig = require('@jupyterlab/galata/lib/playwright-config');
module.exports = {
...baseConfig,
webServer: {
command: 'jlpm start',
url: 'http://localhost:8888/lab',
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI
}
};
================================================
FILE: juvio_frontend/ui-tests/tests/juvio_frontend.spec.ts
================================================
import { expect, test } from '@jupyterlab/galata';
/**
* Don't load JupyterLab webpage before running the tests.
* This is required to ensure we capture all log messages.
*/
test.use({ autoGoto: false });
test('should emit an activation console message', async ({ page }) => {
const logs: string[] = [];
page.on('console', message => {
logs.push(message.text());
});
await page.goto();
expect(
logs.filter(s => s === 'JupyterLab extension juvio_frontend is activated!')
).toHaveLength(1);
});
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "juvio"
version = "0.1.0b2"
description = "Jupyter kernel using uv and per-notebook dependencies"
authors = [{ name = "Oleh Kostromin", email = "oleh@beastbyte.ai" }]
license = { text = "MIT" }
requires-python = ">=3.11"
dependencies = [
"notebook>=7.0,<8.0",
"jupyterlab>=4.0,<5.0",
"ipykernel>=6.25,<8.0",
"aiofiles>=23.0.0"
]
[project.scripts]
juvio-kernel-launcher = "juvio.kernel_launcher:main"
[project.entry-points."jupyter_server.extensions"]
juvio = "juvio:_load_jupyter_server_extension"
[tool.setuptools]
include-package-data = true
[tool.setuptools.packages.find]
where = ["."]
include = ["juvio"]
[tool.setuptools.data-files]
"share/jupyter/labextensions/juvio_frontend" = [
"juvio/labextension/package.json"
]
"share/jupyter/labextensions/juvio_frontend/static" = [
"juvio/labextension/static/509.8b21d569ebbb8f039bdd.js",
"juvio/labextension/static/728.864a8d7732d3e14d284a.js",
"juvio/labextension/static/remoteEntry.cb75fcc929fa00484f36.js",
"juvio/labextension/static/third-party-licenses.json"
]
"share/jupyter/kernels/juvio" = [
"juvio/kernelspec/kernel.json",
]
gitextract_jjcr1hib/ ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── juvio/ │ ├── __init__.py │ ├── content_manager.py │ ├── converter.py │ ├── kernel_launcher.py │ ├── kernelspec/ │ │ └── kernel.json │ ├── labextension/ │ │ ├── package.json │ │ └── static/ │ │ ├── 509.8b21d569ebbb8f039bdd.js │ │ ├── 728.864a8d7732d3e14d284a.js │ │ ├── remoteEntry.cb75fcc929fa00484f36.js │ │ ├── style.js │ │ └── third-party-licenses.json │ ├── server_extension.py │ └── shared.py ├── juvio_frontend/ │ ├── .gitignore │ ├── .prettierignore │ ├── .yarnrc.yml │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── RELEASE.md │ ├── babel.config.js │ ├── install.json │ ├── jest.config.js │ ├── package.json │ ├── pyproject.toml │ ├── setup.py │ ├── src/ │ │ ├── __tests__/ │ │ │ └── juvio_frontend.spec.ts │ │ └── index.ts │ ├── style/ │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ ├── tsconfig.json │ ├── tsconfig.test.json │ └── ui-tests/ │ ├── README.md │ ├── jupyter_server_test_config.py │ ├── package.json │ ├── playwright.config.js │ └── tests/ │ └── juvio_frontend.spec.ts └── pyproject.toml
SYMBOL INDEX (38 symbols across 8 files)
FILE: juvio/__init__.py
function _jupyter_server_extension_points (line 3) | def _jupyter_server_extension_points():
FILE: juvio/content_manager.py
function _is_juvio_file (line 9) | def _is_juvio_file(path):
function juvio_get_sync (line 13) | def juvio_get_sync(self, path, content=True, type=None, format=None, **k...
function juvio_save_sync (line 42) | def juvio_save_sync(self, model, path, **kwargs):
function juvio_get_async (line 102) | async def juvio_get_async(self, path, content=True, type=None, format=No...
function juvio_save_async (line 134) | async def juvio_save_async(self, model, path, **kwargs):
function juvio_rename_file (line 194) | def juvio_rename_file(self, old_path, new_path):
function rename_file (line 210) | async def rename_file(self, old_path, new_path):
function create_juvio_contents_manager_class (line 225) | def create_juvio_contents_manager_class(base_manager_class):
FILE: juvio/converter.py
class JuvioConverter (line 10) | class JuvioConverter:
method _parse_metadata (line 12) | def _parse_metadata(metadata_lines):
method _generate_metadata (line 28) | def _generate_metadata(python_version, dependencies):
method convert_script_to_notebook (line 41) | def convert_script_to_notebook(text: str):
method convert_notebook_to_script (line 113) | def convert_notebook_to_script(nb, dep_metadata: list[str] | None = No...
method create (line 140) | def create(python_version="3.10", dependencies=None):
method extract_metadata (line 155) | def extract_metadata(text: str):
FILE: juvio/kernel_launcher.py
function cmd (line 12) | def cmd(
function main (line 44) | def main():
FILE: juvio/labextension/static/728.864a8d7732d3e14d284a.js
function n (line 1) | function n(e){for(var n=-1,r=0;r<t.length;r++)if(t[r].identifier===e){n=...
function r (line 1) | function r(e,r){for(var a={},i=[],s=0;s<e.length;s++){var c=e[s],u=r.bas...
function o (line 1) | function o(e,t){var n=t.domAPI(t);return n.update(e),function(t){if(t){i...
FILE: juvio/labextension/static/remoteEntry.cb75fcc929fa00484f36.js
function S (line 1) | function S(e){var r=w[e];if(void 0!==r)return r.exports;var t=w[e]={id:e...
function f (line 1) | function f(){return a.pop().replace(/^\((.+)\)$/,"$1")}
FILE: juvio/server_extension.py
function _load_jupyter_server_extension (line 1) | def _load_jupyter_server_extension(app):
FILE: juvio/shared.py
class FileLock (line 16) | class FileLock:
method __init__ (line 17) | def __init__(self, filepath, timeout=10, delay=0.05):
method acquire (line 32) | def acquire(self):
method release (line 63) | def release(self):
method __enter__ (line 74) | def __enter__(self):
method __exit__ (line 78) | def __exit__(self, exc_type, exc_val, exc_tb):
class AsyncFileLock (line 82) | class AsyncFileLock:
method __init__ (line 83) | def __init__(self, filepath, timeout=10, delay=0.05):
method acquire (line 98) | async def acquire(self):
method release (line 128) | async def release(self):
method __aenter__ (line 137) | async def __aenter__(self):
method __aexit__ (line 141) | async def __aexit__(self, exc_type, exc_value, traceback):
function load_ipython_extension (line 145) | def load_ipython_extension(ip):
function start (line 202) | def start():
Condensed preview — 43 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (84K chars).
[
{
"path": ".gitignore",
"chars": 3443,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2025 Oleh Kostromin\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "MANIFEST.in",
"chars": 419,
"preview": "include LICENSE\ninclude README.md\ninclude pyproject.toml\n\n# Include all files from the kernelspec and labextension direc"
},
{
"path": "README.md",
"chars": 2391,
"preview": "<div align=\"center\">\n <img alt=\"logo\" src=\"https://gist.githubusercontent.com/OKUA1/d6e65e883546021ea774857878fd0537/ra"
},
{
"path": "juvio/__init__.py",
"chars": 304,
"preview": "from .server_extension import _load_jupyter_server_extension\n\ndef _jupyter_server_extension_points():\n return [{\"modu"
},
{
"path": "juvio/content_manager.py",
"chars": 9101,
"preview": "import nbformat\nimport inspect\nimport aiofiles\nfrom juvio.converter import JuvioConverter\nfrom juvio.shared import FileL"
},
{
"path": "juvio/converter.py",
"chars": 5746,
"preview": "import sys\nimport nbformat\n\nif sys.version_info >= (3, 11):\n import tomllib\nelse:\n raise ImportError(\"Python 3.11+"
},
{
"path": "juvio/kernel_launcher.py",
"chars": 1724,
"preview": "import os\nimport sys\nimport platform\nfrom juvio.converter import JuvioConverter\n\n\ncurrent_path = os.path.dirname(os.path"
},
{
"path": "juvio/kernelspec/kernel.json",
"chars": 173,
"preview": "{\n \"argv\": [\n \"juvio-kernel-launcher\",\n \"-f\",\n \"{connection_file}\"\n ],\n \"name\": \"juvio\",\n \"di"
},
{
"path": "juvio/labextension/package.json",
"chars": 5293,
"preview": "{\n \"name\": \"juvio_frontend\",\n \"version\": \"0.1.0\",\n \"description\": \"Juvio jupyterlab extension.\",\n \"keywords\": [\n "
},
{
"path": "juvio/labextension/static/509.8b21d569ebbb8f039bdd.js",
"chars": 832,
"preview": "\"use strict\";(self.webpackChunkjuvio_frontend=self.webpackChunkjuvio_frontend||[]).push([[509],{509:(e,o,t)=>{t.r(o),t.d"
},
{
"path": "juvio/labextension/static/728.864a8d7732d3e14d284a.js",
"chars": 3999,
"preview": "\"use strict\";(self.webpackChunkjuvio_frontend=self.webpackChunkjuvio_frontend||[]).push([[728],{56:(e,t,n)=>{e.exports=f"
},
{
"path": "juvio/labextension/static/remoteEntry.cb75fcc929fa00484f36.js",
"chars": 6610,
"preview": "var _JUPYTERLAB;(()=>{\"use strict\";var e,r,t,n,o,i,a,u,f,l,s,d,p,c,h,v,g,b,m,y={678:(e,r,t)=>{var n={\"./index\":()=>t.e(5"
},
{
"path": "juvio/labextension/static/style.js",
"chars": 157,
"preview": "/* This is a generated file of CSS imports */\n/* It was generated by @jupyterlab/builder in Build.ensureAssets() */\n\nimp"
},
{
"path": "juvio/labextension/static/third-party-licenses.json",
"chars": 2453,
"preview": "{\n \"packages\": [\n {\n \"name\": \"css-loader\",\n \"versionInfo\": \"6.11.0\",\n \"licenseId\": \"MIT\",\n \"extr"
},
{
"path": "juvio/server_extension.py",
"chars": 543,
"preview": "def _load_jupyter_server_extension(app):\n app.log.info(\"[juvio] Loading Juvio\")\n from juvio.content_manager import"
},
{
"path": "juvio/shared.py",
"chars": 6955,
"preview": "from ipykernel import kernelapp as app_module\nimport time\nimport platform\nimport asyncio\nimport hashlib\nimport os\nimport"
},
{
"path": "juvio_frontend/.gitignore",
"chars": 1645,
"preview": "*.bundle.*\nlib/\nnode_modules/\n*.log\n.eslintcache\n.stylelintcache\n*.egg-info/\n.ipynb_checkpoints\n*.tsbuildinfo\njuvio_fron"
},
{
"path": "juvio_frontend/.prettierignore",
"chars": 82,
"preview": "node_modules\n**/node_modules\n**/lib\n**/package.json\n!/package.json\njuvio_frontend\n"
},
{
"path": "juvio_frontend/.yarnrc.yml",
"chars": 57,
"preview": "enableImmutableInstalls: false\n\nnodeLinker: node-modules\n"
},
{
"path": "juvio_frontend/CHANGELOG.md",
"chars": 86,
"preview": "# Changelog\n\n<!-- <START NEW CHANGELOG ENTRY> -->\n\n<!-- <END NEW CHANGELOG ENTRY> -->\n"
},
{
"path": "juvio_frontend/LICENSE",
"chars": 1522,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2025, Oleh Kostromin\nAll rights reserved.\n\nRedistribution and use in source and bina"
},
{
"path": "juvio_frontend/README.md",
"chars": 2854,
"preview": "# juvio_frontend\n\n[](-/actions/workflows/build.yml)\nJuvio jupyterla"
},
{
"path": "juvio_frontend/RELEASE.md",
"chars": 2307,
"preview": "# Making a new release of juvio_frontend\n\nThe extension can be published to `PyPI` and `npm` manually or using the [Jupy"
},
{
"path": "juvio_frontend/babel.config.js",
"chars": 68,
"preview": "module.exports = require('@jupyterlab/testutils/lib/babel.config');\n"
},
{
"path": "juvio_frontend/install.json",
"chars": 188,
"preview": "{\n \"packageManager\": \"python\",\n \"packageName\": \"juvio_frontend\",\n \"uninstallInstructions\": \"Use your Python package m"
},
{
"path": "juvio_frontend/jest.config.js",
"chars": 608,
"preview": "const jestJupyterLab = require('@jupyterlab/testutils/lib/jest-config');\n\nconst esModules = [\n '@codemirror',\n '@jupyt"
},
{
"path": "juvio_frontend/package.json",
"chars": 5151,
"preview": "{\n \"name\": \"juvio_frontend\",\n \"version\": \"0.1.0\",\n \"description\": \"Juvio jupyterlab extension.\",\n \"keywords\": [\n "
},
{
"path": "juvio_frontend/pyproject.toml",
"chars": 2280,
"preview": "[build-system]\nrequires = [\"hatchling>=1.5.0\", \"jupyterlab>=4.0.0,<5\", \"hatch-nodejs-version\"]\nbuild-backend = \"hatchlin"
},
{
"path": "juvio_frontend/setup.py",
"chars": 33,
"preview": "__import__('setuptools').setup()\n"
},
{
"path": "juvio_frontend/src/__tests__/juvio_frontend.spec.ts",
"chars": 189,
"preview": "/**\n * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests\n */\n\ndescribe('juvio_frontend', () => {\n it"
},
{
"path": "juvio_frontend/src/index.ts",
"chars": 1329,
"preview": "import {\n JupyterFrontEnd,\n JupyterFrontEndPlugin\n} from '@jupyterlab/application';\n\nimport { ILauncher } from '@jupyt"
},
{
"path": "juvio_frontend/style/base.css",
"chars": 138,
"preview": "/*\n See the JupyterLab Developer Guide for useful CSS Patterns:\n\n https://jupyterlab.readthedocs.io/en/stable/deve"
},
{
"path": "juvio_frontend/style/index.css",
"chars": 20,
"preview": "@import 'base.css';\n"
},
{
"path": "juvio_frontend/style/index.js",
"chars": 21,
"preview": "import './base.css';\n"
},
{
"path": "juvio_frontend/tsconfig.json",
"chars": 623,
"preview": "{\n \"compilerOptions\": {\n \"allowSyntheticDefaultImports\": true,\n \"composite\": true,\n \"declaration\": true,\n \""
},
{
"path": "juvio_frontend/tsconfig.test.json",
"chars": 30,
"preview": "{\n \"extends\": \"./tsconfig\"\n}\n"
},
{
"path": "juvio_frontend/ui-tests/README.md",
"chars": 3758,
"preview": "# Integration Testing\n\nThis folder contains the integration tests of the extension.\n\nThey are defined using [Playwright]"
},
{
"path": "juvio_frontend/ui-tests/jupyter_server_test_config.py",
"chars": 397,
"preview": "\"\"\"Server configuration for integration tests.\n\n!! Never use this configuration in production because it\nopens the serve"
},
{
"path": "juvio_frontend/ui-tests/package.json",
"chars": 427,
"preview": "{\n \"name\": \"juvio_frontend-ui-tests\",\n \"version\": \"1.0.0\",\n \"description\": \"JupyterLab juvio_frontend Integration Tes"
},
{
"path": "juvio_frontend/ui-tests/playwright.config.js",
"chars": 340,
"preview": "/**\n * Configuration for Playwright using default from @jupyterlab/galata\n */\nconst baseConfig = require('@jupyterlab/ga"
},
{
"path": "juvio_frontend/ui-tests/tests/juvio_frontend.spec.ts",
"chars": 521,
"preview": "import { expect, test } from '@jupyterlab/galata';\n\n/**\n * Don't load JupyterLab webpage before running the tests.\n * Th"
},
{
"path": "pyproject.toml",
"chars": 1241,
"preview": "[build-system]\nrequires = [\"setuptools>=64\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"juvio\"\n"
}
]
About this extraction
This page contains the full source code of the OKUA1/juvio GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 43 files (75.3 KB), approximately 21.9k tokens, and a symbol index with 38 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.