Many of our customers use our Python programming library for their projects. This language is very easy to learn, and can be used on all 3 major operating systems. However, distributing an application is a little more complicated, as you need to distribute not only the Python code for the application, but also the dependencies and the virtual machine. Fortunately, there's an open source utility called PyInstaller, which allows you to package everything in an executable. However, to use it with our library, you need to provide it with some additional information.
PyInstaller parses a Python script and determines which libraries and modules are required. It then gathers the application script, all dependencies, and the Python interpreter in a single directory. Finally, a mini-executable is created, which launches the interpreter in this directory with the right parameters to use the contents of this directory only.
The --onefile option is used to create a single, larger executable, which contains the entire contents of this directory. At runtime, the contents of the sub-directory are unzipped into a temporary directory and the application is launched. This option is very convenient, as there is only one file to copy, containing everything you need.
The problem with the Yoctopuce library
PyInstaller has one limitation: it cannot detect the use of dynamic libraries (.dll, .so, and so on) and consequently does not include them in the executable.
The Yoctopuce library uses a dynamic library called "yapi". Normally, the various versions (for different OSes) of this library are located in the cdll sub-directory and are loaded at runtime. With PyInstaller, this sub-directory is not included and the application crashes at runtime with the following message:
C:\tmp\Examples\Doc-Inventory>dist\inventory.exe Traceback (most recent call last): File "inventory.py", line 22, in <module> if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "yoctopuce\yocto_api.py", line 2705, in RegisterHub File "yoctopuce\yocto_api.py", line 2573, in InitAPI File "yoctopuce\yocto_api.py", line 1224, in yloadYapiCDLL ImportError: YAPI shared library is missing (yoctopuce\cdll\yapi64.dll), make sure it is available and accessible. [PYI-38744:ERROR] Failed to execute script 'inventory' due to unhandled exception! </module>
There's obviously a way to correct this problem. You must use PyInstaller's --add-data option to force the cdll directory to be included in the executable. The argument to this option must be in the form "source:dest_dir", where "source" is the file or directory to be included, and "dest_dir" is the destination directory in relation to the application. The two paths are separated by a colon.
Depending on how you downloaded or installed the Yoctopuce library, the value of these two paths is different.
If the library is installed with pip
As explained in the documentation and in our tutorial, the way to include the library is different. Using our PyPi package means prefixing includes with "yoctopuce.". The dynamic yapi libraries must therefore be stored in a yoctopuce/cdlldirectory.
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
sys.exit("init error" + str(errmsg))
print('Device list')
module = YModule.FirstModule()
while module is not None:
print(module.get_serialNumber() + ' (' + module.get_productName() + ')')
module = module.nextModule()
YAPI.FreeAPI()
When the Yoctopuce library is downloaded using the pip tool, you do not exactily know where the library is stored. This directory can be found using the pip tool's "show" command.
Here are the last lines of the result from the "pip show yoctopuce":
C:\tmp\>;pip show yoctopuce ... ... distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Location: C:\Users\seb\AppData\Roaming\Python\Python312\site-packages Requires: Required-by:
The Location attribute corresponds to the directory where all packages are installed. The "yapi" dynamic libraries are therefore stored in the :
"C:\Users\seb\AppData\Roaming\Python\Python312\site-packages\yoctopuce\cdll". The destination directory is yoctopuce/cdll.
With all this information, the complete command is as follows:
pyinstaller --onefile --add-data \Users\seb\AppData\Roaming\Python\Python312\site-packages\yoctopuce\cdll:yoctopuce\cdll inventory.py
This command creates an inventory.exe executable containing everything you need.
If the library is installed manually
If the library is downloaded from our website or from GitHub, there's no need to prefix imports with "yoctopuce.". Simply include yocto_api or yocto_xxx depending on the classes used. Dynamic "yapi" libraries must be stored in a cdll directory.
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
sys.exit("init error" + str(errmsg))
print('Device list')
module = YModule.FirstModule()
while module is not None:
print(module.get_serialNumber() + ' (' + module.get_productName() + ')')
module = module.nextModule()
YAPI.FreeAPI()
The --add-data parameter is easier to determine, as we know where the Yoctopuce library has been installed. For example, if you unzipped the library in the c:\tmp\yoctlib directory, the path of the "cdll" directory is c:\tmp\yoctlib\Sources\cdll. This will give you the "--add-data c:\tmp\yoctlib\Sources\cdll/;cdll" option.
Note that if you modify the sys.path variable in your Python script to add the Yoctopuce library to the search path (as in our examples), you need to use the -p option with the path of the Yoctopuce source files. PyInstaller isn't smart enough to realize this trick, and you have to manually update the search path.
In this case, use -p c:\tmp\yoctlib\Sources. This option is not necessary if the yocto_xxx.py files are in Python's search path or if they are in the current directory, but if in doubt, it's better to set it systematically.
The final command is:
pyinstaller.exe --onefile -p :\tmp\yoctlib\Sources --add-data "c:\tmp\yoctlib\Sources/cdll/;cdll" inventory.py
As with pip, PyInstaller generates an inventory.exe executable that includes everything needed for the program to run.
Conclusion
PyInstaller facilitates the distribution of Python applications by gathering the code, dependencies, and interpreter into one executable. When used with our programming library, you need to add the --add-data option to force inclusion of the dynamic yapi library.