Everything needed to run your Python code using the PyXLL add-in can be packaged together and deployed to other users of your organization.
PyXLL is licensed per-user so you should check your license covers all the users of the add-in. If you need to add more users to your license you can do so using the customer portal, or contact us if you are not sure.
Warning
Redistribution of the PyXLL add-in to unlicensed users is not permitted by the Software License Agreement.
For the add-in to work your end users will need to have the following. Each can be pre-configured and packaged so that the end user doesn’t need to install each individually or do any configuration themselves:
One of the benefits of using PyXLL is that the code is separated from the Excel workbooks so that updates to the code can be deployed without having to change each workbook that depends on it.
There are various ways to make the items listed above available to your end users. Which methods you choose will depend on your specific use case and requirements. It’s quite usual to end up using a combination of the methods described below.
This is the simplest option as it allows your Python code to be deployed centrally, ensuring that all users see the same code at all times.
As long as a user can read from the network drive, PyXLL can be configured to read Python modules from there. This ensures that you don’t need to copy code around and that all users are always referencing the same version of your code.
The PyXLL configuration can be shared by using the external_config
option in the pyxll.cfg file
(see Using a common pyxll.cfg file). You can list multiple external configs which will get merged together when
PyXLL loads.
The PyXLL config file itself can also be on a network drive. The environment variable PYXLL_CONFIG_FILE
can be set to tell PyXLL to load a config file from somewhere other than the default location. You can
use environment variables inside the config file so that logging gets written to the user’s local storage.
When deploying code on a network drive you will want to make the folder read-only to your users to ensure no accidental updates occur.
To update the Python code, rather than updating in-place it is good practice to create a new folder with the updated code and then change the pythonpath in the shared config file to that new folder. This way if there are any problems then you can quickly revert to the previous folder, and it also avoids problems with certain files (e.g. dll and pyd files) becoming locked while users have then open.
A typical structure for this shared folder would be (with the folder names changed to suit your requirements):
modules-{version}
The folder containing your Python code
python-{version}
(Optional) Folder containing your Python environment
shared_pyxll.cfg
This would specify pythonpath = ./modules-{version}
, your modules list, any other shared settings, and optionally
executable = .\python-{version}\pythonw.exe
if you are including the Python environment on the network drive.
In your main pyxll.cfg
file you would include the shared_pyxll.cfg file using
external_config = X:\network\folders\pyxll\shared_pyxll.cfg
.
You would choose whether or not to include the Python environment on the shared folder or not depending on whether or not each user would already have a suitable local Python environment and how fast your network drive is. Loading Python from a slow network drive can slow down starting Excel, in which case one of the options below may be more suitable.
To install the PyXLL add-in you can either load the pyxll.xll add-in manually into Excel, or your could script
it using the pyxll activate
command (see Adding the PyXLL add-in to Excel).
Similarly to putting everything on a network drive, as above, you can combine everything into a single zip archive for distribution to your users. You can include everything, including a Python environment, and pre-configure PyXLL to reference it using a relative path.
To install the PyXLL add-in you would unzip the archive on the PC where you want it to be installed
and then load the PyXLL add-in in Excel. This can be scripted, and you can script the step of adding
PyXLL to Excel using the pyxll activate
command (see Adding the PyXLL add-in to Excel).
Once installed, updates can be made using a Startup Script. See Using a startup script to install and update Python code for more details about that.
A typical structure for this zip file would be (changing the folder names to suit your requirements):
modules
The folder containing your Python code
python
Folder containing your Python environment
pyxll.xll
The PyXLL add-in
pyxll.cfg
This would specify everything relative to the location of this file. For example,
pythonpath = .\modules
and executable = .\python\pythonw.exe
.
Once you have a zip file containing the above you could use a batch script to do the installation. The script would do the following:
your_pyxll_archive.zip
to C:\Your\Local\PyXLL\Install
C:\Your\Local\PyXLL\Install\python\python.exe -m pyxll activate --non-interactive C:\Your\Local\PyXLL\Install\pyxll.xll
This script could be run directly by your end users to install the add-in, or pushed out by your systems team or IT administrators. It could even be configured to run each time the user logs in.
If you prefer to build an installer or MSI instead of using a zip file and script as above then that is also possible. Some organizations prefer this method as they already have mechanisms for pushing out installers to users’ PCs.
As with the zip file approach above, the Python runtime can be bundled alongside PyXLL and your Python code into a single standalone installer.
For detailed instructions and an example project for building an MSI installer, see the pyxll-installer project on GitHub.
The PyXLL config be shared so that each user gets the same configuration, and so updates to the config can be made once rather than on each PC. This is done by setting the external_config option in the pyxll.cfg file.
Each user still has their own pyxll.cfg file with any settings specific to them (if any), but they also use the external_config option to source in one or more shared configs.
The external config can be a file on a network drive or a URL.
[PYXLL]
external_config = https://intranet/pyxll/pyxll-shared.cfg
If more than one external config is required the external_config setting accepts a list of files and URLs.
If it is not desirable for each user to have their own pyxll.cfg file then
the environment variable PYXLL_CONFIG_FILE
can be set to tell PyXLL
where to load the config from. This could be a path on a network drive or a URL.
When using a shared config typically you don’t want the log file to be written to the same place for every user. You can use environment variables in the config file to avoid this, eg
[LOG]
path = %(USERPROFILE)s/pyxll/logs
See Environment Variables for more details.
Importing Python code from a network drive can have some disadvantages. It requires a fast network, and even then it can be slow to import the modules. It may also be against your coroprate IT policy to deploy code via a network drive because it lacks sufficient control, or it just may not suit your deployment needs.
Using a startup script you can check what version of your Python code is currently deployed and download the latest if necessary. Once downloaded the code is on the local PC and so importing it will be fast. When updates are needed the script will detect there’s a newer version of the code available and download it.
Such a script might look something like this:
SET VERSION=v1
SET PYTHON_FOLDER=.\python-code-%VERSION%
REM No need to download anything if we already have the latest
IF EXIST %PYTHON_FOLDER% THEN GOTO END
REM Download and unzip the latest code
wget https://intranet/pyxll/python-code-%VERSION%.tar.gz
tar -xzf python-code-%VERSION%.tar.gz --directory %PYTHON_FOLDER%
ECHO Latest code has been downloaded to .\python-code-%VERSION%
:END
The above script is just an illustration and your script would be different depending on your needs. It could also be a Powershell script rather than a plain batch script.
To get this script to run when Excel starts we use the startup_script option in the pyxll.cfg file. This is set to the the path of the script to run, or it can be a URL. By using a URL (or a location on a network drive) whenever we want to deploy a different version of the code to all of our users we only have to update the version number in the script.
[PYXLL]
startup_script = https://intranet/pyxll/startup-script.cmd
Now the script runs when Excel starts, but the code downloaded isn’t on our Python Path and so won’t be able to be imported. Because we’re using a different folder for each version of the code we can’t hard-code the path in our pyxll.cfg file.
Within a startup script run by PyXLL you can run various commands, including getting and
setting PyXLL options. There’s a command pyxll-set-option
that we can use to set
the pythonpath
option to the correct folder:
SET VERSION=v1
SET PYTHON_FOLDER=.\python-code-%VERSION%
ECHO pyxll-set-option PYTHON pythonpath %PYTHON_FOLDER%
The pyxll-set-option
command is run by echoing it from the batch script. PyXLL sees this in
the output from the script and updates the pythonpath
option. Calling pyxll-set-option
for a multi-line option like pythonpath
appends to it rather than replacing it.
There are several other commands available from a startup script. See Startup Script for more details.
The Python environment and many of the Python packages your code depends on are likely to change less often than your main Python code. They do still need to be available to PyXLL for it to work however.
This doesn’t mean that Python actually needs to be installed on the local PC.
PyXLL can be configured to use any Python environment as long as it is accessible by the user. This means you can take a Python environment and copy it to a network drive and have PyXLL reference it from there. For example, where below X: is a mapped network drive:
[PYTHON]
executable = X:\PyXLL\Python\pythonw.exe
As long as the Python environment on the network drive is complete, this will work fine.
A very useful tool for creating a Python environment suitable for being relocated to a network drive is conda-pack.
Note, using a venv
doesn’t create a complete Python environment and still requires the
base Python install and so cannot be used in this way.
Referencing the Python environment from a network drive will not be as fast to load as if it was installed on the local PC. Another option is to use the startup_script option and copy a Python environment locally on demand when Excel starts.
A startup script that downloads a Python environment would look something along the lines of the following:
SET VERSION=v1
SET PYTHON_ENV=.\python37-%VERSION%
REM No need to download anything if we already have the latest
IF EXIST %PYTHON_ENV% THEN GOTO DONE
REM Download and unzip the Python environment
wget https://intranet/pyxll/python37-%VERSION%.tar.gz
tar -xzf python37-%PYTHON_ENV%.tar.gz --directory %PYTHON_ENV%
ECHO Latest Python environment has been downloaded to .\python37-%VERSION%
:DONE
REM Set the PyXLL executable option
ECHO pyxll-set-option PYTHON executable %PYTHON_ENV%\pythonw.exe
Once you have made your Python code and Python environment available to your end user, either by copying them to the local PC or by making them available on the network drive, you will need to add the PyXLL add-in so that it gets loaded each time Excel starts.
This can either be done manually, if the end user is comfortable managing their own Excel add-ins, or it can be scripted for them.
To install the PyXLL add-in from a script you can use the pyxll activate sub-command.
The activate
sub-command installs the PyXLL Excel add-in into Excel so that it is loaded whenever Excel starts.
When using the pyxll
command from a specific Python environment, rather than the system default or currently active
environment, it can be easier to use python -m pyxll
instead of running the pyxll
command directly. For example,
if you have the PyXLL add-in copied locally to C:\PyXLL\pyxll.xll
and Python copied locally as
C:\PyXLL\python\python.exe
you would run:
> C:\PyXLL\python\python.exe -m pyxll activate --non-interactive C:\PyXLL\pyxll.xll
The --non-interactive
switch prevents the pyxll activate
command from asking the user for input or confirmation which
makes it suitable to be called from a script.
When distributing Python code it is usual to package it up into a wheel file using setuptools
. This
allows consumers of your package to install it easily using pip (the Python package manager).
You can distribute a Python package containing PyXLL functionality in the same way. To avoid the end user of your package from having to manually configure their pyxll.cfg file, PyXLL looks for its entry points in any installed packages.
The entry points are configured in your setup.py file used to build your package. PyXLL supports two
entry points, pyxll.modules
and pyxll.ribbon
.
A simple setup.py file to build a package called your_package
might look as follows:
from setuptools import setup, find_packages
setup(
name="your_package",
description="Your package description",
version="0.0.1",
packages=find_packages(),
entry_points={
"pyxll": [
"modules = your_package:pyxll_modules",
"ribbon = your_package:pyxll_ribbon"
]
}
)
To build a wheel using your setup.py file you run python setup.py bdist_wheel
.
The user of your package would install the wheel by running pip install <wheel file>
.
The entry points listed in this setup.py file are your_package:pyxll_modules
for the pyxll/modules
entry
point and your_package:pyxll_ribbon
for the pyxll/ribbon
entry point.
Each entry point is a reference to a function. It’s these functions that PyXLL will call to configure itself to load your package automatically without the consumer of your package having to modify their pyxll.cfg file.
The modules entry point is a function that returns a list of module names for PyXLL to import when it loads.
In the above your_package
example, suppose your_package
contained two sub-modules your_package.xlfuncs
and your_package.xlmacros
that you want to be loaded when PyXLL starts. To make that happen you would write
the your_package.pyxll_modules
entry point function return both packages.
def pyxll_modules():
"""entry point referenced in setup.py"""
return [
"your_package.xlfuncs",
"your_package.xlmacros",
]
This is of course just an example. The entry point function could be in any package (including a subpackage) that
you configure in your setup.py
file.
The ribbon entry point can be used to add ribbon controls to the Excel ribbon in addition to whatever ribbon controls are configured in the pyxll.cfg file.
The ribbon entry point function should return either a single ribbon xml resource or a list of ribbon xml resources. These will be merged with any other ribbon files loaded and combined to create the custom ribbon UI in Excel.
See Customizing the Ribbon for the specifics of how to create a ribbon xml file.
In the above your_package
example, suppose you had also included a “ribbon.xml” resource in the wheel
and you wanted to add that to the Excel ribbon. Your ribbon entry point would load the XML data from
the resource (or it could load it from a file) and return that for PyXLL to use when building the ribbon.
import pkg_resources
def pyxll_ribbon():
"""entry point referenced in setup.py"""
# Load the XML resource
ribbon_xml = pkg_resources.resource_string(__name__, "ribbon.xml")
# Return the ribbon XML resource for PyXLL to load
return ribbon_xml
If you are using files instead of package resources then you can also tell PyXLL the filename of the XML file. If you have images referenced in your ribbon xml using relative paths then providing the filename will ensure that PyXLL can load the images relative to the correct path.
import os
def pyxll_ribbon():
"""entry point referenced in setup.py"""
# Get the ribbon XML filename
ribbon_file = os.path.join(os.path.dirname(__file__), "ribbon.xml")
# Load the xml data
with open(ribbon_file) as fh:
ribbon_xml = fh.read()
# Return the ribbon XML resource with its file name for PyXLL to load
return (ribbon_file, ribbon_xml)
When using the load_image
function as your image loaded in the ribbon xml file, images can
be referenced either by filename or as a package resource name if you are building them into your package.
If you have multiple ribbon resources then the pyxll.ribbon
entry point function may return a list of
resources or a list of (filename, resource) tuples.
Warning
If your Python packages are on a network drive it can be slow to look for entry points, which may result in slow start times for Excel.
You can prevent PyXLL from looking for entry points by setting the following in your pyxll.cfg file:
[PYXLL]
ignore_entry_points = 1