setup
Setup transforms an empty directory into a fully-configured nbdev project: GitHub repository created, virtual environment configured, Jupyter kernel registered, direnv wired up, documentation themed, and first commit pushed. The entire ceremony compressed into atomic execution.
The workflow follows a strict sequence—each step depends on the previous one’s success. We create the GitHub repo first (establishing the remote), scaffold nbdev structure locally, then configure the development environment to work with both.
Future Architecture
TODO: Refactor init_nbdev into composable functions
The current implementation is monolithic. Future structure should be: - create_github_repo() - Step 1 - scaffold_nbdev_structure() - Steps 2-2b - setup_python_environment() - Steps 3-6 - configure_project_automation() - Step 7-8 - initial_sync() - Steps 9-10 - launch_dev_servers() - Post-init optional servers
TODO: Extract server launching to separate command
Launching Jupyter and nbdev_preview isn’t part of initialization—it’s a development convenience. Should be pj dev or pj launch, callable anytime during the project lifecycle. This also fixes the venv issue (use project’s jupyter, not global).
See placeholders below for future implementation.
launch_dev_servers
launch_dev_servers (project_path, args)
*Launch Jupyter Lab and nbdev_preview in project venv
TODO: Implement as separate pj dev command - Use project_path/.venv/bin/jupyter (not global) - Manage port allocation - Handle –no-preview, –no-lab, –code flags - Track PIDs for pj kill*
Project Initialization
The main initialization orchestrator. Currently monolithic; future refactoring will decompose into the functions scaffolded above.
init_nbdev
init_nbdev (args)
Initialize a new nbdev project with full configuration.
The Initialization Sequence
Steps 1-2: Remote and local scaffolding
We create the GitHub repository first, then run nbdev_new inside the cloned directory. This order matters: gh repo create --clone gives us a git-initialized directory with remote tracking configured. Running nbdev_new afterward populates that structure without fighting over git initialization.
Step 2b: Theme injection
The dark theme must be applied after nbdev_new creates the nbs/ directory structure but before the first nbdev_prepare run. Quarto reads these theme files when rendering documentation.
Steps 3-6: Python environment
The venv must exist before we can install packages into it. We explicitly specify --python .venv/bin/python to avoid the “active venv poisoning” problem—if the user has another venv active, uv pip install would target that instead of our project’s .venv.
Jupyter kernel registration requires ipykernel to be installed in the venv first. The kernel name matches the project name, making it obvious which kernel belongs to which project.
Steps 7-8: Automation wiring
direnv auto-activates the venv when entering the project directory. The .envrc file is simple: just source .venv/bin/activate. Running direnv allow whitelists this specific .envrc file (direnv’s security model).
The .gitignore update is defensive: nbdev_new might not add .venv/, and we definitely don’t want to commit the init log or virtualenv to git.
Steps 9-10: Initial sync
nbdev_prepare exports notebooks to modules, runs tests, cleans metadata, and builds docs. Running it before the first commit ensures the initial state is clean. The check=False allows it to fail without aborting—some test failures are acceptable at this stage (e.g., empty notebooks).
We use git add -A not git commit -am because -am only stages modified files, missing the new files that nbdev_prepare created (exported modules, generated docs).
Post-init server launching
Currently inline; should be extracted to launch_dev_servers() and eventually become pj dev command. Note the bug: uses global jupyter-lab instead of .venv/bin/jupyter lab. This will be fixed in the refactored version.
The shell exec trick
os.execvp() replaces the current process with a new shell, landing the user in the project directory. This avoids nested shells (running pj init multiple times doesn’t stack shells). The trade-off: the user must exit to return to their original directory.