Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 68 additions & 55 deletions .github/workflows/build-publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
name: Build KeplerGL Python and NPM Packages
name: Build and Publish KeplerGL Python Package

on: push
on:
push:
workflow_dispatch:
inputs:
prerelease:
description: 'Publish as prerelease (uncheck for official release)'
required: false
type: boolean
default: true

jobs:
build_and_publish:
Expand All @@ -11,76 +19,81 @@ jobs:
steps:
- uses: actions/checkout@v4

# use Volta to manage yarn/node versions
- uses: volta-cli/action@v4
- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python 3.9
uses: actions/setup-python@v2
- name: Set up Python 3.11
run: uv python install 3.11

- name: Setup Node.js
uses: actions/setup-node@v4
with:
python-version: 3.9
node-version: '20'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install twine virtualenv
- name: Install Python dependencies
working-directory: bindings/python
run: uv sync --dev

- name: Build KeplerGL
- name: Install npm dependencies
working-directory: bindings/python
run: npm ci

- name: Build TypeScript
working-directory: bindings/python
env:
MapboxAccessTokenJupyter: ${{ secrets.mapbox_jupyter_token }}
NODE_OPTIONS: --openssl-legacy-provider
run: |
python -m virtualenv venv
source venv/bin/activate
pip install jupyter jupyterlab jupyter-packaging
cd bindings/kepler.gl-jupyter
python setup.py sdist
run: npm run build

- name: Type check
working-directory: bindings/python
run: npm run typecheck

- name: Build Python package
working-directory: bindings/python
run: uv build

- name: Test KeplerGL
working-directory: bindings/python
run: |
source venv/bin/activate
pip install bindings/kepler.gl-jupyter/dist/*.tar.gz
if [ ! -f "venv/share/jupyter/nbextensions/keplergl-jupyter/index.js" ]; then
venv/bin/jupyter nbextension install --py --sys-prefix keplergl
venv/bin/jupyter nbextension enable --py --sys-prefix keplergl
fi
venv/bin/jupyter nbconvert --execute bindings/kepler.gl-jupyter/notebooks/DataFrame.ipynb --to python
python bindings/kepler.gl-jupyter/notebooks/DataFrame.py
uv pip install dist/*.whl
uv run pytest

- name: Create artifact
uses: actions/upload-artifact@v4
with:
name: keplergl-pypi
path: bindings/kepler.gl-jupyter/dist/
path: bindings/python/dist/

- name: Check Release Tag
id: check-tag
- name: Check version format
if: github.event_name == 'workflow_dispatch'
working-directory: bindings/python
run: |
if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+([a|b][0-9])?-jupyter$ ]]; then
echo ::set-output name=publish::true
VERSION=$(grep -Po '(?<=^version = ")[^"]+' pyproject.toml)
echo "Package version: $VERSION"

# Check if version contains prerelease indicators (a, b, rc, dev)
if [[ "$VERSION" =~ (a|b|rc|dev)[0-9]+ ]]; then
IS_PRERELEASE_VERSION=true
else
IS_PRERELEASE_VERSION=false
fi

echo "Is prerelease version: $IS_PRERELEASE_VERSION"
echo "Publishing as prerelease: ${{ inputs.prerelease }}"

# Fail if mismatch between version format and publish type
if [[ "${{ inputs.prerelease }}" == "true" && "$IS_PRERELEASE_VERSION" == "false" ]]; then
echo "::error::Publishing as prerelease but version '$VERSION' does not have a prerelease suffix (e.g., 0.4.0a1, 0.4.0b1, 0.4.0rc1)"
exit 1
fi

if [[ "${{ inputs.prerelease }}" == "false" && "$IS_PRERELEASE_VERSION" == "true" ]]; then
echo "::error::Cannot publish official release with prerelease version '$VERSION'. Please update the version in pyproject.toml."
exit 1
fi

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'

- name: Publish kepler-jupyter to NPM
if: steps.check-tag.outputs.publish == 'true'
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
MapboxAccessTokenJupyter: ${{ secrets.mapbox_jupyter_token }}
run: |
source venv/bin/activate
cd bindings/kepler.gl-jupyter/js
echo "//registry.npmjs.org/:_authToken=${{ secrets.npm_token }}" > .npmrc
npm config set registry https://registry.npmjs.org/
npm whoami
npm config ls
npm publish --verbose --access public

- name: Publish KeplerGL to Pypi
if: steps.check-tag.outputs.publish == 'true'
- name: Publish to PyPI
if: github.event_name == 'workflow_dispatch'
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: bindings/kepler.gl-jupyter/dist/
packages-dir: bindings/python/dist/
124 changes: 124 additions & 0 deletions bindings/python/DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Development Guide

This guide explains how to set up kepler.gl for Jupyter for local development.

## Prerequisites

- Python >= 3.9
- Node.js (for building the frontend)
- JupyterLab >= 4.0 or Notebook >= 7.0

## Installation

Navigate to the `bindings/python` directory:

```bash
cd bindings/python
```

### 1. Install JavaScript dependencies and build the frontend

```bash
npm install
npm run build
```

### 2. Install Python package in development mode

Using **uv** (recommended):

```bash
uv sync --dev
```

Or using **pip**:

```bash
pip install -e ".[dev]"
```

### 3. Start Jupyter

```bash
# With uv
uv run jupyter lab

# Or with pip
jupyter lab
```

## Development with Hot Reload

For active development with automatic rebuilding of the TypeScript/JavaScript:

```bash
# Terminal 1: Watch for TypeScript changes
npm run dev

# Terminal 2: Run Jupyter
uv run jupyter lab
```

The `npm run dev` command watches for changes in the `src/` directory and automatically rebuilds the widget JavaScript.

## Quick Test

```python
from keplergl import KeplerGl

# Create a map
map = KeplerGl(height=400)
map
```

## Running Tests

```bash
uv run pytest
```

## Available npm Scripts

- `npm run build` - Build TypeScript to `keplergl/static/`
- `npm run dev` - Build with watch mode for development
- `npm run typecheck` - Run TypeScript type checking
- `npm run lint` - Run ESLint on source files

## Publishing to PyPI

Publishing is done manually via GitHub Actions using the "Run workflow" button.

### Version Format

The version in `pyproject.toml` determines what type of release you can publish:

- **Prerelease versions**: Must have a suffix like `a`, `b`, `rc`, or `dev` followed by a number
- Examples: `0.4.0a1` (alpha), `0.4.0b1` (beta), `0.4.0rc1` (release candidate), `0.4.0.dev1` (development)
- **Official versions**: Clean semantic versions without any suffix
- Examples: `0.4.0`, `1.0.0`

### Publishing Steps

1. **Update the version** in both files:
- `pyproject.toml` (line: `version = "x.x.x"`)
- `keplergl/_version.py` (line: `__version__ = "x.x.x"`)

2. **Go to GitHub Actions** → "Build and Publish KeplerGL Python Package" workflow

3. **Click "Run workflow"** and select the appropriate option:
- **Prerelease** (checked by default): Publishes to PyPI as a prerelease. Version must have a prerelease suffix.
- **Official release** (unchecked): Publishes to PyPI as an official release. Version must be a clean version.

4. The workflow will:
- Build and test the package
- Validate that the version format matches the publish type
- Publish to PyPI if validation passes

### Validation Rules

| Checkbox | Version | Result |
|----------|---------|--------|
| ✓ Prerelease | `0.4.0a1` | ✅ Publishes |
| ✓ Prerelease | `0.4.0` | ❌ Fails (version must have prerelease suffix) |
| ☐ Official | `0.4.0` | ✅ Publishes |
| ☐ Official | `0.4.0a1` | ❌ Fails (version must not have prerelease suffix) |
41 changes: 41 additions & 0 deletions bindings/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# kepler.gl for Jupyter

[![PyPI version](https://img.shields.io/pypi/v/keplergl.svg)](https://pypi.org/project/keplergl/)
[![PyPI prerelease](https://img.shields.io/pypi/v/keplergl.svg?include_prereleases&label=prerelease)](https://pypi.org/project/keplergl/#history)

This is the [kepler.gl](http://kepler.gl) Jupyter widget, an advanced geospatial visualization tool for rendering large-scale interactive maps in Jupyter Notebook and JupyterLab.



## Installation

```shell
pip install keplergl
```

### Prerequisites
- Python >= 3.9
- JupyterLab >= 4.0 or Notebook >= 7.0

## Quick Start

```python
from keplergl import KeplerGl

# Create a map
map = KeplerGl(height=400)

# Add data
map.add_data(data=df, name='my_data')

# Display the map
map
```

## Documentation

For full documentation, visit [https://docs.kepler.gl/docs/keplergl-jupyter](https://docs.kepler.gl/docs/keplergl-jupyter).

## License

MIT
56 changes: 56 additions & 0 deletions bindings/python/esbuild.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as esbuild from 'esbuild';
import path from 'path';
import {fileURLToPath} from 'url';
import {createRequire} from 'module';

const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));

const isWatch = process.argv.includes('--watch');

// Resolve apache-arrow to a single location
const arrowPath = path.dirname(require.resolve('apache-arrow'));

// Resolve browser polyfills for Node.js built-ins (point to actual entry files)
const assertPath = require.resolve('assert/');
const eventsPath = require.resolve('events/');

const buildOptions = {
entryPoints: {widget: 'src/index.ts'},
bundle: true,
format: 'esm',
outdir: 'keplergl/static',
loader: {
'.ts': 'ts',
'.tsx': 'tsx',
'.css': 'css'
},
external: [],
minify: !isWatch,
sourcemap: isWatch,
target: ['es2020'],
define: {
'process.env.NODE_ENV': isWatch ? '"development"' : '"production"',
'global': 'globalThis'
},
inject: ['./src/process-shim.js'],
// Use alias to ensure single instance of apache-arrow and provide browser polyfills
alias: {
'apache-arrow': arrowPath,
'assert': assertPath,
'events': eventsPath
}
};

async function build() {
if (isWatch) {
const ctx = await esbuild.context(buildOptions);
await ctx.watch();
console.log('Watching for changes...');
} else {
await esbuild.build(buildOptions);
console.log('Build complete!');
}
}

build().catch(() => process.exit(1));
6 changes: 6 additions & 0 deletions bindings/python/keplergl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Kepler.gl Jupyter Widget."""

from .widget import KeplerGl
from ._version import __version__

__all__ = ["KeplerGl", "__version__"]
3 changes: 3 additions & 0 deletions bindings/python/keplergl/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Version information."""

__version__ = "0.4.0rc1"
Loading
Loading