ModuleNotFoundError exception for a snap built in classic confinement mode

I am in the process of developing and building my first snap and I started with the devmode confinement for it and got it working. I then switched it to classic confinement (because the snap requires access to a wide variety of devices under /dev to do its job). However, I get a ModuleNotFoundError exception for the python module of the application being snapped.

Below is the snapcraft.yaml that I have created.

name: glucometerutils
summary: A command-line utility to interact with glucometers.
description: |
  This provides a command-line utility to interact with a number of
  blood sugar meters (glucometers) models from various manufacturers.
adopt-info: glucometerutils
grade: stable
confinement: classic
base: core20
architectures:
  - build-on: [amd64]
    run-on: [amd64]

apps:
  glucometer:
    command: bin/glucometer
    plugs:
      - block-devices
      - hidraw
      - raw-input
      - raw-usb
      - removable-media
      - scsi-generic
      - serial-port

parts:
  glucometerutils:
    source: https://github.com/glucometers-tech/glucometerutils.git
    override-pull: |
      snapcraftctl pull
      snapcraftctl set-version "$(git rev-parse HEAD | cut -c -10)"
    plugin: python
    python-packages:
      - attrs
      - construct
      - crcmod
      - freestyle-hid>=1.0.2
      - hidapi
      - PYSCSI[sgio]>=2.0.1
      - pyserial[cp2110]>=3.5b0

The exception that I am getting only with the classic mode build is

Traceback (most recent call last):
  File "/snap/glucometerutils/x1/bin/glucometer", line 5, in <module>
    from glucometerutils.glucometer import main
ModuleNotFoundError: No module named 'glucometerutils'

I do not see this error when building the snap with devmode confinement. I am not sure if I am missing anything obvious here. Help!

You can stage python3.8-minimal, set a symlink from $SNAPCRAFT_PART_INSTALL/bin/python3 to ../usr/bin/python3.8 in override-build and execute the command with the python interpreter bundled in the snap (by defining the app command as bin/python $SNAP/bin/glucometer). Something like:

apps:
  glucometer:
    command: bin/python $SNAP/bin/glucometer
...

parts:
  glucometerutils:
    ...
    override-build: |
      snapcraftctl build
      ln -sf ../usr/bin/python3.8 $SNAPCRAFT_PART_INSTALL/bin/python3
    ...
    stage-packages:
      - libpython3-stdlib
      - libpython3.8-minimal
      - libpython3.8-stdlib
      - python3-pip
      - python3-setuptools
      - python3-wheel
      - python3-venv
      - python3.8-minimal
      - python3-distutils
      - python3-minimal
1 Like

@cmatsuoka, thank you for your suggestion. It resolved the issue that I was having. :tada:

However, as someone new to the snap ecosystem and building snaps, I am wondering why these manual steps are needed in the first place even when I have explicitly mentioned that I am using the python plugin. The extra packages that you have suggested installing are a part of any typical python installation and I do not understand why they need to be manually installed even though I am explicitly specifying that I am building a part using the python plugin, which, imho, should have transparently taken care of installing and setting up the relevant dependencies?

And why does this issue not pop up when I built and installed the snap in the devmode confinement, but does when I build it in the classic confinement. Do you have any idea?

Is there some documentation/examples that I can read up to understand the issue and the solution better?

I suspect the only change that was required to fix your issue was to add the explicit call to the python executable in the command: entry. Without that you were using the #! on the script you were calling which is likely set to /usr/bin/python or /usr/bin/python3 - both of these will be outside your snap in a classic snap so they will not know about and likely be incompatible with the python modules in your snap.

1 Like

The python plugin doesn’t know about the classic snap concept – in fact, for core22 part building is handled by a separate library that’s also used by other applications such as Charmcraft. The proposed change runs the snapped Python code using the bundled Python interpreter (as @lucyllewy mentioned) since the host system may have an incompatible version or not have a Python interpreter installed at all. The extra packages are used to add the Python interpreter to the snap. When you execute a snap using non-classic confinement you don’t need to do that because the root filesystem is predictable:

$ sudo snap run --shell word-salad
root@hiccup:/home/claudio# grep NAME /etc/os-release
NAME="Ubuntu Core"
PRETTY_NAME="Ubuntu Core 18"

For other examples you can check Snapcraft itself or Charmcraft, they’re both Python applications that use classic confinement.

1 Like