AGENTS.md#
A guide for AI coding agents working in the
jupyter-incubator/sparkmagicrepository. Covers project structure, development setup, key commands, architecture, conventions, and common pitfalls.
Project Overview#
Sparkmagic is a set of tools for interactively working with remote Spark clusters in Jupyter notebooks . Rather than requiring Spark to be installed on the Jupyter server, sparkmagic communicates with Spark entirely through a REST server. Three server implementations are currently supported: Livy (for Yarn), Lighter (for Yarn or Kubernetes, PySpark only), and Ilum (for Yarn or Kubernetes) .
There are two ways to use sparkmagic :
- Via the IPython kernel β a
%%sparkmagic runs code against a remote cluster from a normal IPython notebook. - Via wrapper kernels β dedicated PySpark, Scala (
sparkkernel), and SparkR kernels that automatically connect to a remote cluster, execute code and SQL queries, manage Livy sessions, and generate automatic visualizations.
Three-Package Monorepo#
The repository is a monorepo containing three independently-installable Python packages, each with its own setup.py, setup.cfg, and requirements.txt. All three are currently at version 0.23.0. Their dependency chain flows bottom-up:
sparkmagic
βββ depends on β hdijupyterutils
βββ depends on β autovizwidget
βββ depends on β hdijupyterutils
| Package | Directory | Purpose |
|---|---|---|
| hdijupyterutils | hdijupyterutils/ | Foundational utility library: configuration management, events, logging, file I/O, IPython display helpers, and widget factories. No inter-package dependencies. |
| autovizwidget | autovizwidget/ | Auto-visualization library for Pandas dataframes using Plotly. Provides the interactive chart widget used by sparkmagic kernels. Depends on hdijupyterutils. |
| sparkmagic | sparkmagic/ | The main package. Provides the Livy REST client, wrapper kernels (PySpark, Scala, SparkR), %%spark magic commands, auth plugins (Kerberos, Basic, None), a server extension for programmatic cluster management, and the controller widget UI. Depends on both hdijupyterutils and autovizwidget. |
The root-level pyproject.toml is a Poetry workspace that pulls all three packages together for local development convenience β it is not the authoritative package definition for any of the three .
Development Setup#
Prerequisites#
-
Python β₯ 3.8 (CI tests against 3.8, 3.9, 3.10, 3.11, and 3.12)
-
libkrb5-dev (Linux) β required to build
requests-kerberos. On Ubuntu/Debian:sudo apt-get install -y libkrb5-devOn macOS, install via Homebrew:
brew install krb5
Option A: Direct Editable Installs (Recommended)#
This mirrors what CI does. Install packages in dependency order β order matters:
pip install --upgrade pip
pip install pytest mock
# Install in order: foundation first, then dependents
pip install -r hdijupyterutils/requirements.txt -e hdijupyterutils
pip install -r autovizwidget/requirements.txt -e autovizwidget
pip install -r sparkmagic/requirements.txt -e sparkmagic
The -e flag installs each package in editable mode, meaning changes to source files take effect immediately without reinstalling.
Option B: Poetry Virtual Environment#
The root pyproject.toml configures a Poetry workspace that installs all three packages in develop mode in a single step:
poetry install
# If you hit numpy/pandas resolution errors:
poetry run pip install numpy pandas
poetry install
Post-Install: Create the Config Directory#
The sparkmagic package reads from ~/.sparkmagic/. This directory must exist before running tests:
mkdir -p ~/.sparkmagic
# Optional but helpful: copy the example config
cp sparkmagic/example_config.json ~/.sparkmagic/config.json
Optional: Register Jupyter Kernels and Server Extension#
To use sparkmagic interactively in Jupyter after installing:
# Find the sparkmagic install location
pip show sparkmagic
# Install wrapper kernels (run from the sparkmagic install location)
jupyter-kernelspec install sparkmagic/kernels/sparkkernel
jupyter-kernelspec install sparkmagic/kernels/pysparkkernel
jupyter-kernelspec install sparkmagic/kernels/sparkrkernel
# Enable the server extension (for programmatic cluster management)
jupyter server extension enable --py sparkmagic
Docker Development Setup#
For a fully integrated local environment with both Jupyter and a Livy-backed Spark instance:
# Build and start the full stack
docker compose build
docker compose up
This brings up two containers :
sparkβ Livy 0.7.1 REST server onhttp://localhost:8998, backed by a local-mode Spark instancejupyterβ Jupyter notebook onhttp://localhost:8888, with sparkmagic pre-installed
Inside the notebook, configure your sparkmagic endpoint to http://spark:8998.
Development mode β to test local code changes in Docker without publishing to PyPI, edit docker-compose.yml and set dev_mode to "true", then rebuild:
# docker-compose.yml
services:
jupyter:
build:
args:
dev_mode: "true" # <-- change this
Then:
docker compose build # rebuild with local editable packages
docker compose up
In dev mode, the Jupyter container installs local hdijupyterutils, autovizwidget, and sparkmagic with the editable flag, so you can make further edits inside the container for real-time debugging.
Key Commands#
Running Tests#
Tests are run per-package by pointing pytest at the package's top-level directory (not the tests/ subdirectory directly):
# Run all three test suites
pytest hdijupyterutils
pytest autovizwidget
pytest sparkmagic # requires ~/.sparkmagic to exist
# Run a single test file
pytest sparkmagic/sparkmagic/tests/test_livysession.py
# Run tests with verbose output
pytest -v sparkmagic
# Run all tests (via Poetry)
poetry run pytest
Note:
pytest sparkmagicmust be preceded bymkdir -p ~/.sparkmagicif the directory doesn't yet exist, otherwise config-related tests will fail.
Linting and Formatting#
The project uses Black for code formatting. This is enforced as a CI check on every push and PR :
# Check formatting (dry run)
black --check .
# Apply formatting
black .
There is no separate lint step beyond Black β no flake8, mypy, or isort configuration is present in the repo.
Build Distributions#
To build PyPI distribution artifacts for publishing (mirrors what the publish CI workflow does):
python -m build hdijupyterutils
python -m build autovizwidget
python -m build sparkmagic
Each command produces dist/ inside the respective package directory.
Docker Commands#
# Build Docker images
docker compose build
# Start full stack (Jupyter on :8888, Livy on :8998)
docker compose up
# Start in background
docker compose up -d
# Stop and remove containers
docker compose down
# Rebuild after local code changes (with dev_mode: "true")
docker compose build && docker compose up
Editable Install (Refresh)#
If you add new files or entry points to a package, you may need to re-run the editable install to pick up changes:
pip install -e hdijupyterutils
pip install -e autovizwidget
pip install -e sparkmagic
Architecture & Structure#
How It Works#
Sparkmagic uses Livy (or a compatible REST server) to remotely execute all user code . No Spark components need to be installed on the Jupyter server itself. The data flow is:
Jupyter Notebook Cell
β
sparkmagic kernel / %%spark magic
β (HTTP REST)
Livy Server ββ Remote Spark Cluster
β
JSON / plain-text response
β
sparkmagic (deserializes, auto-visualizes via autovizwidget)
β
Rendered output in notebook
Key architectural properties :
- No local Spark β code runs entirely on the remote cluster driver via Livy
- Multi-language β Python, Scala, and R kernels are equally featured; sparkmagic serializes all I/O
- Multiple endpoints β a single notebook can open sessions against different clusters in different languages
- Structured data via JSON β SQL query results are serialized to JSON, parsed by sparkmagic, then passed to autovizwidget for visualization or converted to Pandas DataFrames
Repository Layout#
sparkmagic/ # repo root
β
βββ hdijupyterutils/ # Package 1
β βββ hdijupyterutils/ # source
β β βββ configuration.py # JSON config reader/writer
β β βββ events.py # event publishing
β β βββ filehandler.py # MagicsFileHandler for logging
β β βββ ipythondisplay.py # IPython display helpers
β β βββ logger.py # logging setup
β β βββ tests/ # unit tests
β βββ setup.py / setup.cfg
β βββ requirements.txt
β
βββ autovizwidget/ # Package 2
β βββ autovizwidget/ # source
β β βββ plotlygraphs/ # Plotly chart renderers
β β βββ widget/ # encoding widget (interactive UI)
β β βββ utils/ # dataframe utilities
β β βββ tests/ # unit tests
β βββ setup.py / setup.cfg
β βββ requirements.txt
β
βββ sparkmagic/ # Package 3
β βββ sparkmagic/ # source
β β βββ livyclientlib/ # Livy HTTP client, session, retry policy
β β βββ kernels/ # wrapper kernels
β β β βββ pysparkkernel/ # PySpark kernel definition
β β β βββ sparkkernel/ # Scala kernel definition
β β β βββ sparkrkernel/ # SparkR kernel definition
β β β βββ wrapperkernel/ # base kernel logic shared by all three
β β βββ magics/ # sparkmagics.py β %%spark, %%sql, etc.
β β βββ auth/ # kerberos.py, basic.py, customauth.py
β β βββ controllerwidget/ # UI widget for cluster/session management
β β βββ serverextension/ # /reconnectsparkmagic REST API handler
β β βββ utils/ # constants, dataframe utils
β β βββ tests/ # ~25 unit tests
β βββ example_config.json # reference configuration
β βββ setup.py / setup.cfg
β βββ requirements.txt
β
βββ examples/ # sample notebooks
βββ helm/ # Kubernetes Helm charts
βββ screenshots/ # docs screenshots
βββ docker-compose.yml
βββ Dockerfile.jupyter
βββ Dockerfile.spark # Spark + Livy server
βββ pyproject.toml # Poetry dev workspace
βββ CHANGELOG.md
βββ RELEASING.md
βββ README.md
Configuration#
Sparkmagic reads ~/.sparkmagic/config.json at startup. Key configuration areas :
| Section | Purpose |
|---|---|
kernel_python_credentials / kernel_scala_credentials / kernel_r_credentials | Livy endpoint URL and auth credentials per language |
authenticators | Maps auth type names to implementation classes (Kerberos, Basic_Access, None) |
wait_for_idle_timeout_seconds / livy_session_startup_timeout_seconds | Session timeout controls |
session_configs | Default Spark session settings (driverMemory, executorCores, etc.) |
use_auto_viz / max_results_sql | Visualization behavior |
retry_policy / retry_seconds_to_sleep_list | Retry/backoff behavior for Livy calls |
logging_config | Standard Python logging dict config |
The example config lives at sparkmagic/example_config.json . In Docker dev mode, the Jupyter container copies it to ~/.sparkmagic/config.json and replaces localhost with spark (the Docker hostname) .
Server Extension#
Sparkmagic registers a Jupyter server extension that exposes a /reconnectsparkmagic POST endpoint. This allows programmatic cluster switching β useful for multi-tenant environments and orchestration tools . It is enabled via:
jupyter server extension enable --py sparkmagic
Development Conventions#
Code Style#
- Formatter: Black β all Python code must be formatted with Black. This is enforced automatically on every push and pull request via GitHub Actions .
- Run
black .from the repo root before committing. The CI lint check will fail on any unformatted code. - No separate flake8, mypy, or isort configurations exist in this repository.
Testing Practices#
Tests live alongside source code inside each package:
<pkg>/<pkg>/tests/test_<module>.py
For example: sparkmagic/sparkmagic/tests/test_livysession.py .
Test patterns to follow:
- Use
pytestas the test framework; functions are namedtest_<behavior>() - Use
setup_function()andteardown_function()for module-level test setup/teardown - Use
MagicMockfrom themocklibrary for all dependency injection and object stubbing - Use
pytest.raises()for expected exception testing - For parallel-safe test classes, add
_multiprocess_can_split_ = Trueclass attribute - New tests go in the
tests/subdirectory of the relevant package β not a top-leveltests/directory
When to add tests: Every code change should include corresponding unit tests. The PR checklist explicitly requires it .
Pull Request Workflow#
The .github/pull_request_template.md defines the required checklist for every PR :
- Write a description of what changed and why
- Format with Black β run
black .and commit the result - Update
CHANGELOG.mdβ add a bullet point at the top describing the change - Add or modify unit tests β all behavior changes need test coverage
- Manually test with a notebook β integration-test your change end-to-end in Jupyter
- For new features: add an example notebook in
examples/and/or updateREADME.md
CI runs tests on Python 3.8 through 3.12 . All test matrix versions must pass before merge.
Naming Conventions#
- Test files:
test_<module_name>.py(mirrors the source file being tested) - Test functions:
test_<behavior_being_tested>() - Auth plugins: implement in
sparkmagic/auth/following the pattern of existing modules (basic.py,kerberos.py,customauth.py) - New kernels: follow the
<language>kernel/subdirectory structure undersparkmagic/kernels/
Versioning#
All three packages are versioned together (currently 0.23.0). Version numbers live in:
hdijupyterutils/hdijupyterutils/__init__.pyautovizwidget/autovizwidget/__init__.pysparkmagic/sparkmagic/__init__.py
Releases are triggered manually via the GitHub Actions Release workflow , which bumps all versions using bumpversion and tags the commit. Do not manually edit version numbers β use the release workflow.
Gotchas & Common Issues#
1. Install Order Is Mandatory#
The three packages have hard inter-package dependencies. Installing them in the wrong order will fail:
# β Wrong β autovizwidget requires hdijupyterutils
pip install -e autovizwidget
# β Correct order
pip install -r hdijupyterutils/requirements.txt -e hdijupyterutils
pip install -r autovizwidget/requirements.txt -e autovizwidget
pip install -r sparkmagic/requirements.txt -e sparkmagic
2. ~/.sparkmagic Directory Must Exist Before Running Tests#
The sparkmagic test suite will fail with file-not-found errors if ~/.sparkmagic/ does not exist. Always create it first:
mkdir -p ~/.sparkmagic
This is why CI explicitly runs mkdir ~/.sparkmagic before pytest sparkmagic . This is not needed for hdijupyterutils or autovizwidget tests.
3. libkrb5-dev Is Required on Linux#
The sparkmagic package depends on requests-kerberos, which requires Kerberos development headers. Without them, pip install -r sparkmagic/requirements.txt will fail to build on Linux:
sudo apt-get install -y libkrb5-dev # Ubuntu/Debian
On macOS: brew install krb5 and set the relevant env vars. In Docker, Kerberos is installed via conda .
4. pytest sparkmagic β Not pytest sparkmagic/sparkmagic/tests#
pytest should be pointed at the package root directory, not the tests subdirectory. The CI commands are:
pytest hdijupyterutils # β
pytest autovizwidget # β
pytest sparkmagic # β
# also works, but not the canonical form:
# pytest sparkmagic/sparkmagic/tests
5. Docker dev_mode Must Be a String "true", Not a Boolean#
The docker-compose.yml passes dev_mode as a build arg to Dockerfile.jupyter. The Dockerfile checks if [ "$dev_mode" = "true" ] β it expects the literal string "true", not YAML boolean true:
# docker-compose.yml
args:
dev_mode: "true" # β string
# dev_mode: true # β YAML boolean β will NOT enable dev mode
After changing dev_mode, you must run docker compose build β docker compose up alone will not pick up the change.
6. pyproject.toml Is a Dev Convenience, Not the Package Source of Truth#
The root pyproject.toml is a Poetry workspace for local development that wraps all three packages. It is not the authoritative source for any package's metadata, dependencies, or entry points. Those live in each package's setup.py/setup.cfg:
hdijupyterutils/setup.pyandhdijupyterutils/setup.cfgautovizwidget/setup.pyandautovizwidget/setup.cfgsparkmagic/setup.pyandsparkmagic/setup.cfg
When modifying dependencies or entry points, edit the per-package files β not pyproject.toml.
7. Editable Installs Don't Auto-Pick-Up New Package Data#
If you add new package_data entries (e.g., new kernel JSON files), you need to re-run pip install -e <package> to re-register them. Simply editing a source file is sufficient for code changes, but not for resource/data file additions.
8. The Docker Spark Image Uses an Old Spark Version (Intentionally)#
Dockerfile.spark is pinned to Spark 2.4.7 and Livy 0.7.1 . This is intentional β Livy 0.7.1 requires Scala 2.11, which constrains the Spark version. Do not upgrade these without verifying Livy compatibility.
9. Livy Endpoint Is spark:8998 Inside Docker, localhost:8998 Outside#
The example config at sparkmagic/example_config.json defaults Livy to http://localhost:8998 . The Docker container's Jupyter image patches this automatically with sed -i 's/localhost/spark/g' . If you're configuring sparkmagic manually inside Docker, use http://spark:8998 as the endpoint .
10. Three Separate PyPI Packages, But One Repo Version Tag#
Despite being a monorepo, each package is published to PyPI separately with independent tokens. The release workflow bumps all three versions simultaneously using bumpversion, so they stay in sync . If you need to test a local version of just one package against the published versions of the others, install the local one with -e and the others normally β but be careful about version constraint mismatches in requirements.txt.