Modern approach to update an old package relying on PyCall and custom script to insall a python package

I am the maintainer of OdsIO, an old package that wrap ezodf python module to read and write data on ODS (LibreOffice) spreadsheets.
This has been my first package and at that time I used PyCall and made the installation of the python module on build.jl:

using PyCall

println("Running build.jl for the OdsIO package.")

# Change that to whatever packages you need.
const PACKAGES = ["ezodf","lxml"]

# Use eventual proxy info
proxy_arg=String[]
if haskey(ENV, "http_proxy")
    push!(proxy_arg, "--proxy")
    push!(proxy_arg, ENV["http_proxy"])
end

# Import pip
try
    pyimport("pip")
catch
    # If it is not found, install it
    println("Pip not found on your sytstem. Downloading it.")
    get_pip = joinpath(dirname(@__FILE__), "get-pip.py")
    download("https://bootstrap.pypa.io/get-pip.py", get_pip)
    run(`$(PyCall.python) $(proxy_arg) $get_pip --user`)
end

println("Installing required python packages using pip")
run(`$(PyCall.python) $(proxy_arg) -m pip install --user --upgrade pip setuptools`)
run(`$(PyCall.python) $(proxy_arg) -m pip install --user $(PACKAGES)`)

Now this approach has several limitations, and I am trying to “modernize it”.
So, which is the standard way nowaday to create a Julia package that require a Python one?

I am trying (unsuccessfully) to use PythonCall/CondaPkg, by replacing the text above on build.jl with

import CondaPkg
CondaPkg.add("ezodf")

and then using

CondaPkg.add("ezodf")
const ezodf = pyimport("ezodf")
const pyio  = pyimport("io")

in src/OdsIO.jl
However I have various errors (from not knowing the ezods package or completelly crashing Julia.

So which is the approach in order to get a Julia package wrapping a Python module, without depending at all to what there is installed in the host system (i.e. whatever the host system has its own ezods module or not) ?

I managed but my package is not running in VSCode.
Actually, it has nothing to do with my package.

The following script runs in a julia terminal but not in VSCode, both with Julia 1.12.6:

using Pkg
Pkg.activate(;temp=true)
Pkg.add(["CondaPkg","PythonCall"])
using PythonCall
import CondaPkg

CondaPkg.add("ezodf")
const ezodf_ref = Ref{Py}()
# This will go into the __init__ function in my package..
ezodf_ref[] = pyimport("ezodf")

ezodf   = ezodf_ref[]
destDoc = ezodf.newdoc(doctype="ods", filename="foo.ods") # here it crashes Julia only in VSCode

Do you have an idea what the hell is happening ?

This is the output on the command line:

$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.12.6 (2026-04-09)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org release
|__/                   |

julia> using Pkg

julia> Pkg.activate(;temp=true);
  Activating new project at `/tmp/jl_x6kU8l`

julia> Pkg.add(["CondaPkg","PythonCall"]);
   Resolving package versions...
    Updating `/tmp/jl_x6kU8l/Project.toml`
  [992eb4ea] + CondaPkg v0.2.36
  [6099a3de] + PythonCall v0.9.34
    Updating `/tmp/jl_x6kU8l/Manifest.toml`
  [992eb4ea] + CondaPkg v0.2.36
  [9a962f9c] + DataAPI v1.16.0
  [e2d170a0] + DataValueInterfaces v1.0.0
  [82899510] + IteratorInterfaceExtensions v1.0.0
  [692b3bcd] + JLLWrappers v1.8.0
  [682c06a0] + JSON v1.6.0
  [1914dd2f] + MacroTools v0.5.16
  [0b3b1443] + MicroMamba v0.1.15
  [bac558e1] + OrderedCollections v1.8.1
  [69de0a69] + Parsers v2.8.4
  [fa939f87] + Pidfile v1.3.0
  [aea7be01] + PrecompileTools v1.3.4
  [21216c6a] + Preferences v1.5.2
  [6099a3de] + PythonCall v0.9.34
  [6c6a2e73] + Scratch v1.3.0
  [ec057cc2] + StructUtils v2.8.2
  [3783bdb8] + TableTraits v1.0.1
  [bd369af6] + Tables v1.12.1
  [e17b2a0c] + UnsafePointers v1.0.0
  [f8abcde7] + micromamba_jll v2.3.1+0
  [4d7b5844] + pixi_jll v0.41.3+0
  [0dad84c5] + ArgTools v1.1.2
  [56f22d72] + Artifacts v1.11.0
  [2a0f44e3] + Base64 v1.11.0
  [ade2ca70] + Dates v1.11.0
  [f43a241f] + Downloads v1.7.0
  [7b1f6079] + FileWatching v1.11.0
  [b77e0a4c] + InteractiveUtils v1.11.0
  [ac6e5ff7] + JuliaSyntaxHighlighting v1.12.0
  [4af54fe1] + LazyArtifacts v1.11.0
  [b27032c2] + LibCURL v0.6.4
  [76f85450] + LibGit2 v1.11.0
  [8f399da3] + Libdl v1.11.0
  [56ddb016] + Logging v1.11.0
  [d6f4376e] + Markdown v1.11.0
  [ca575930] + NetworkOptions v1.3.0
  [44cfe95a] + Pkg v1.12.1
  [de0858da] + Printf v1.11.0
  [9a3f8284] + Random v1.11.0
  [ea8e919c] + SHA v0.7.0
  [9e88b42a] + Serialization v1.11.0
  [f489334b] + StyledStrings v1.11.0
  [fa267f1f] + TOML v1.0.3
  [a4e569a6] + Tar v1.10.0
  [8dfed614] + Test v1.11.0
  [cf7118a7] + UUIDs v1.11.0
  [4ec0a83e] + Unicode v1.11.0
  [e66e0078] + CompilerSupportLibraries_jll v1.3.0+1
  [deac9b47] + LibCURL_jll v8.15.0+0
  [e37daf67] + LibGit2_jll v1.9.0+0
  [29816b5a] + LibSSH2_jll v1.11.3+1
  [14a3606d] + MozillaCACerts_jll v2025.11.4
  [458c3c95] + OpenSSL_jll v3.5.4+0
  [83775a58] + Zlib_jll v1.3.1+2
  [8e850ede] + nghttp2_jll v1.64.0+1
  [3f19e933] + p7zip_jll v17.7.0+0

julia> using PythonCall;
    CondaPkg Found dependencies: /home/lobianco/.julia/packages/PythonCall/JksWe/CondaPkg.toml
    CondaPkg Found dependencies: /home/lobianco/.julia/packages/CondaPkg/lKlVY/CondaPkg.toml
    CondaPkg Resolving changes
             + libstdcxx
             + libstdcxx-ng
             + openssl
             + python
    CondaPkg Initialising pixi
             │ /home/lobianco/.julia/artifacts/cefba4912c2b400756d043a2563ef77a0088866b/bin/pixi
             │ init
             │ --format pixi
             └ /tmp/jl_x6kU8l/.CondaPkg
✔ Created /tmp/jl_x6kU8l/.CondaPkg/pixi.toml
    CondaPkg Wrote /tmp/jl_x6kU8l/.CondaPkg/pixi.toml
             │ [dependencies]
             │ openssl = ">=3, <3.6"
             │ libstdcxx = ">=3.4,<=15.1"
             │ libstdcxx-ng = ">=3.4,<=15.1"
             │ 
             │     [dependencies.python]
             │     channel = "conda-forge"
             │     build = "*cp*"
             │     version = ">=3.10,!=3.14.0,!=3.14.1,<4"
             │ 
             │ [project]
             │ name = ".CondaPkg"
             │ platforms = ["linux-64"]
             │ channels = ["conda-forge"]
             │ channel-priority = "strict"
             └ description = "automatically generated by CondaPkg.jl"
    CondaPkg Installing packages
             │ /home/lobianco/.julia/artifacts/cefba4912c2b400756d043a2563ef77a0088866b/bin/pixi
             │ install
             └ --manifest-path /tmp/jl_x6kU8l/.CondaPkg/pixi.toml
✔ The default environment has been installed.

julia> import CondaPkg;

julia> CondaPkg.add("ezodf");
    CondaPkg Found dependencies: /tmp/jl_x6kU8l/CondaPkg.toml
    CondaPkg Found dependencies: /home/lobianco/.julia/packages/PythonCall/JksWe/CondaPkg.toml
    CondaPkg Found dependencies: /home/lobianco/.julia/packages/CondaPkg/lKlVY/CondaPkg.toml
    CondaPkg Resolving changes
             + ezodf
    CondaPkg Initialising pixi
             │ /home/lobianco/.julia/artifacts/cefba4912c2b400756d043a2563ef77a0088866b/bin/pixi
             │ init
             │ --format pixi
             └ /tmp/jl_x6kU8l/.CondaPkg
✔ Created /tmp/jl_x6kU8l/.CondaPkg/pixi.toml
    CondaPkg Wrote /tmp/jl_x6kU8l/.CondaPkg/pixi.toml
             │ [dependencies]
             │ openssl = ">=3, <3.6"
             │ libstdcxx = ">=3.4,<=15.1"
             │ libstdcxx-ng = ">=3.4,<=15.1"
             │ ezodf = "*"
             │ 
             │     [dependencies.python]
             │     channel = "conda-forge"
             │     build = "*cp*"
             │     version = ">=3.10,!=3.14.0,!=3.14.1,<4"
             │ 
             │ [project]
             │ name = ".CondaPkg"
             │ platforms = ["linux-64"]
             │ channels = ["conda-forge"]
             │ channel-priority = "strict"
             └ description = "automatically generated by CondaPkg.jl"
    CondaPkg Installing packages
             │ /home/lobianco/.julia/artifacts/cefba4912c2b400756d043a2563ef77a0088866b/bin/pixi
             │ install
             └ --manifest-path /tmp/jl_x6kU8l/.CondaPkg/pixi.toml
✔ The default environment has been installed.

julia> const ezodf_ref = Ref{Py}()
Base.RefValue{Py}(#undef)

julia> # This will go into the __init__ function in my package..
       ezodf_ref[] = pyimport("ezodf")
/tmp/jl_x6kU8l/.CondaPkg/.pixi/envs/default/lib/python3.14/site-packages/ezodf/tableutils.py:31: SyntaxWarning: "\d" is an invalid escape sequence. Such sequences will not work in the future. Did you mean "\\d"? A raw string is also an option.
  CELL_ADDRESS = re.compile('^([A-Z]+)(\d+)$')
Python: <module 'ezodf' from '/tmp/jl_x6kU8l/.CondaPkg/.pixi/envs/default/lib/python3.14/site-packages/ezodf/__init__.py'>

julia> ezodf   = ezodf_ref[]
Python: <module 'ezodf' from '/tmp/jl_x6kU8l/.CondaPkg/.pixi/envs/default/lib/python3.14/site-packages/ezodf/__init__.py'>

julia> destDoc = ezodf.newdoc(doctype="ods", filename="foo.ods")
Python: <ezodf.document.PackagedDocument object at 0x780148993620>

julia> println("done!")
done!

And this is the screenshot I managed to get of the VSCode stack error:

For some obscure reason I had to add this on CondaPkg.toml in the package root directory to limit Python version.

    [deps.python]
    version = ">=3.10,<3.13"

Still unsure why this was giving an issue only in VSCode, but.. problem solved