Manage Julia dependency in a Python module
Project description
julia_project
This package provides the class JuliaProject
for managing a
Julia project that lives inside
a Python package.
julia_project
supports two libraries for calling Julia from Python,
pyjulia (the Python module "julia")
and
juliacall.
julia_project
is in pypi; it can be installed via pip install julia_project
. It is meant to be used as a library
in other projects.
julia_project
is meant to provide some automation, hand holding, and error checking in managing
a Julia dependency in a Python package.
For the user of a package that uses julia_project
Suppose the Python module mymodule
uses julia_project
to manage its Julia dependency.
The user of mymodule
can do the following to import mymodule and install and initialize
the Julia project that mymodule
depends on.
import mymodule
mymodule.project.ensure_init()
See the docstring for ensure_init
for optional arguments.
The author of mymodule
may have already called ensure_init
as step peformed when
import mymodule
is executed. In this case, calling ensure_init
again is a no-op.
To compile, or recompile, the Julia project, the user calls mymodule.project.compile()
.
The compiled Julia system image will be used the next time mymodule
is imported, speeding up
both startup and the first execution of code.
Calling mymodule.project.clean()
removes the compiled system image and some other files.
This will force again resolving the Julia package requirements on the next import mymodule
.
Calling mymodule.project.clean_all()
will remove the entire project tree.
This is a kind of "factory reset". The next time you run project.ensure_init()
a new directory will be created
and populated with files from the installation directly.
Calling mymodule.project.update()
checks for compatible updates of Julia packages that
are direct or indirect dependencies of mymodule
, and performs the update.
If you want to handle installation and initialization of the Julia project and packages yourself, you can do
import mymodule
mymodule.project.disable_init()
Then subsequent calls to ensure_init
, explicit or otherwise will do nothing. project.enable_init()
will enable initialization if it has been disabled.
If someone else has called mymodule.project.disable_init()
and you want to override it, you
can call mymodule.project.enable_init()
.
Choosing pyjulia or juliacall
Pass either "juliacall" or "pyjulia" as the argument calljulia
to ensure_init
.
For example
import mymodule
myjuliamod.project.ensure_init(calljulia="juliacall")
Using julia_project
to call Julia functions
A Python-package author can use find_julia
to provide a custom interface to Julia resources.
The author may provide an full-featured or thin interface. In any case it is sometimes
useful to access the Julia/Python interface library directly.
You can also get the imported Python library, either julia
(i.e. pyjulia
) or juliacall
like this
myjuliamod.project.julia
For example, the Julia module Main
may be accessed like this.
Main = myjuliamod.project.julia.Main
Main.sind(90) # 1.0
The semantics and syntax of Python modules julia
and juliacall
are quite different.
But, julia_project
provides a minimal common layer.
For example,
Example = project.simple_import("Example")
imports the Julia module Example
.
Some parts of managing the Julia project are particular to either pyjulia
or
juliacall
. These are handled by the classes PyJulia
and JuliaCall
.
And project.calljulia
is an instance of one of these.
For the author of a package using julia_project
The intended use is as follows.
You want to create a Python package that calls some Julia packages via pyjulia.
You create a directory representing the top level of a Python package,
with a setup.py
and requirements.txt
and the Python code
in a directory mymodule
.
You create a file ./mymodule/Project.toml
describing the Julia packages for the project.
In a Python source file in ./mymodule/
, you create an instance of julia_project.JuliaProject
that manages the Julia project. Call this instance project
and import it into mymodule.
For example, in _julia_project.py
, you might have project = julia_project.JuliaProject([args])
.
And in __init__.py
of mymodule
you have from _julia_project.py import project
.
(See the example directory).
What julia_project does
Then import mymodule; mymodule.project.ensure_init()
will do the following
- Look for the Julia executable in various places using
find_julia
- Offer to download and install Julia if it is not found.
- Optionally create a private Julia depot for
mymodule
to avoid possible issues withPyCall
in different Python environments. - Check that the
julia
(orjuliacall
) package is installed. I.e. check thatPyCall
, orPythonCall
is installed and built, etc. - Optionally download and install a Julia registry.
- Optionally load a custom Julia system image.
- Instantiate the Julia project.
- Provide a Python function that compiles a system image that will be found the next
time
mymodule
is imported. The scripts and environment for compilation are found in a specified subdirectory of the Python project. - Write info about all of the above to a log file
Using julia_project to create a project
Here is a brief example. See the example directory for a complete example.
- Include the following in a file loaded in
./mymodule/
, that is, the directory found byimport mymodule
.
import os
mymodule_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# This just creates an object, but does none of the steps above.
project = JuliaProject(
name="mymodule",
package_path=mymodule_path,
registries = {"MyModuleRegistry" : "git@github.com:myuser/MyModuleRegistry.git"},
logging_level = logging.INFO # or WARN, or ERROR,
)
# If the following is omitted, the user of mymodule must call it explicitly.
project.ensure_init() # This exectutes all the management features listed above
- Create
./mymodule/Project.toml
(or./mymodule/JuliaProject.toml
) for the Julia project.
Compiling
Make a folder ./mymodule/sys_image/
. Add a file ./mymodule/sys_image/Project.toml
(or ./mymodule/sys_image/JuliaProject.toml
)
This typically contains the same dependencies as the top-level Project.toml
. Perhaps a few more or less.
Add a script ./mymodule/sys_image/packages.jl
containing an Array{Symbol}
of packages to be
included in the image.
[:APackage, :AnotherPackage]
-
You don't need to include
PyCall
orPythonCall
. -
Optionally include a file
compile_exercise_script.jl
that will passed asprecompile_execution_file
. -
After compiling, the system image file will be renamed from
sys_julia_project.so
(ordll
, ordylib
) to a name that includes the version of the julia exectuable that built it. The latter is the file name that will be searched for the next time you importmymodule
. -
The project is compiled by calling the method
JuliaProject.compile
either explicitly or during the installation.
Arguments to JuliaProject
name,
package_path,
registries=None,
version_spec="^1.6",
strict_version=True,
sys_image_dir="sys_image",
sys_image_file_base=None,
calljulia="pyjulia",
env_prefix="JULIA_PROJECT_",
post_init_hook=None,
depot=None,
logging_level=None,
console_logging=False
name
-- the name of the module, e.g. "mymodule". Used only in the logger and the name of the system image.package_path
-- path to the top level ofmymodule
.registry_url
-- ifNone
then no registry will be installed (other than the General registry, if not already installed.)version_spec
-- A julia version compatibility specification. The julia executable must satisfy this specification.strict_version
-- IfTrue
prerelease (development) versions of Julia are disallowed when applyingversion_spec
.sys_image_dir
-- the directory in which scripts for compiling a system image, and the system images, are found. This is relative to the top level ofmymodule
.sys_image_file_base
-- the base name of the Julia system image. The system image file will besys_image_file_base + "-" + a_julia_version_string + ".ext"
, whereext
is the dynamic lib extension for your platform.calljulia
-- The julia-from-python interface library. One of two Python packages "pyjulia" and "juliacall".env_prefix
-- Prefix for environment variables to set project optionsdepot
-- IfTrue
, then a private depot in themymodule
installation directory will be used.post_init_hook
-- A function that will be called immediately beforeensure_init
returns.logging_level
-- ifNone
then no logging will be done. iflogging.INFO
, then detailed info will be loggedconsole_logging
-- ifTrue
, then the log messages are echoed to the console.
Environment variables
-
In the following, the prefix
JULIA_PROJECT_
may be changed with the argumentenv_prefix
described above. This allows you to set environment variables specific to each project that do not interfere. -
JULIA_PROJECT_JULIA_PATH
may be set to the path to a Julia executable. This will override other possible paths to a Julia executable. -
JULIA_PROJECT_INSTALL_JULIA
may be set toy
orn
. If set, then no interactive query is done to install Julia viajill.py
. Instead the valuey
orn
is used. -
JULIA_PROJECT_COMPILE
may be set toy
orn
. If set, then no interactive query is done to compile a system image after installing packages. Instead the valuey
orn
is used. -
JULIA_PROJECT_LOG_PATH
may be set to the path to the log file. -
JULIA_PROJECT_DEPOT
-- If set toy
, then a private Julia depot will be created in a directorydepot
under themymodule
installation directory. The depot contains all downloaded registries, packages, precompiled packages, and many other data related to your julia installation. Set ton
to use the standard depot. If it is unset, you may be prompted for your choice.
Location of julia executable
JuliaProject
will look in the following locations, in order
-
The environment variable
JULIA_PROJECT_JULIA_PATH
. WithJULIA_PROJECT_
optionally replaced byenv_prefix
described above. -
In the package top level for the installation
./julia/
-
A julia installation from
jill.py
, with preferred versions specified as above. -
Your system or shell PATH variable.
-
A fresh installation of julia via
jill.py
after asking if you want to download and install.
Building PyCall
Installing and using PyCall
is sometimes easy and sometimes confusing. The latter happens if
you try to use PyCall
with different Python environments. The whole issue can be avoided
by using a private, or Python-package-specific Julia "depot".
Any of the following will create and use such a depot.
-
Enable a new depot by passing the argument
depot=True
when initializing yourJuliaProject
instance. -
The user can set the environment variable
JULIA_PROJECT_DEPOT
described above. -
If no
PyCall.jl
is found, the option to create the package-specific depot will be given. -
If there is a libpython conflict detected during installation you will be prompted to create a depot.
The new depot will be used each time mymodule
is imported. Remove or rename the directory mymodule/depot
to prevent this.
When using a new depot, registries, packges, cached precompiled files, and many other things are stored
in the installation directory of the project, e.g. mymodule
.
This is a heavy solution because it involves duplicating many files if you use Julia for other projects, with Python or not.
But, it does not require that the end user understand anything about the status of your Julia installation, libpython, PyCall.jl
,
etc.
Using a private depot should also allow julia_project
to work with conda environments.
Testing
TESTS ARE OUTDATED!
You can run tests like this:
pytest -p julia.pytestplugin julia_project/tests
Tests that don't require a Julia installation may be run like this:
pytest --no-julia -p julia.pytestplugin julia_project/tests
Warning
This package is very new and is neither well tested nor documented.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for julia_project-0.1.27-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 96f4f08a3937ba0abc994c42c93d203489b2357664c9cf8754f6a917910e7ad0 |
|
MD5 | e56c1af8b4797de484fac22ca7b8be0d |
|
BLAKE2b-256 | fb61468d47ba394cf9c20da045dbdcc41f8b65a9e2fcd97ddf1ecf3389c0e087 |