I’ve been developing jupy-cli, which is a cross-platform CLI for projects that use both Julia and Python.
The design deliberately keeps native package managers:
Julia → Pkg.jl → Project.toml / Manifest.toml
Python → pip → .venv / requirements.txt
Rather than replacing either resolver, jupy-cli provides a project-level layer above them.
For example:
jupip install numpy
runs pip through the Python interpreter belonging to the current hybrid project, while:
jupy analysis.py
jupy simulation.jl
runs either Python or Julia under the same project identity. PythonCall.jl is configured to use that same .venv.
Here is a nice diagram:
jupy
│
project/
│
┌─────────────┴─────────────┐
│ │
Julia Python
│ │
Pkg.jl pip
│ │
Project.toml .venv/
Manifest.toml requirements.txt
│ │
└──────── PythonCall ───────┘
So jupip is not purely a Python package manager. It was built to provide controlled environment targeting and bookkeeping around python -m pip
I’ve been calling it a meta-manager because pip remains responsible for Python packages, Pkg.jl for Julia packages, and PythonCall for interoperability; jupy-cli simply coordinates the relationship among them.
There’s prior work here in PythonCall/JuliaCall, CondaPkg, JuliaPkg, and tools like Pixi. I’ve summarized how the approaches differ in the repository’s documentation.
I’d be interested in feedback on whether this is a sound way to preserve normal pip/venv semantics inside a larger multi-language project, and what Python packaging edge cases this model should account for.