Python GUIs: Packaging PySide6 applications into a macOS app with PyInstaller (updated for 2022)

Planet Python - Sun, 2022-05-01 05:00

There is not much fun in creating your own desktop applications if you can't share them with other people — whether than means publishing it commercially, sharing it online or just giving it to someone you know. Sharing your apps allows other people to benefit from your hard work!

The good news is there are tools available to help you do just that with your Python applications which work well with apps built using PySide6. In this tutorial we'll look at the most popular tool for packaging Python applications: PyInstaller.

This tutorial is broken down into a series of steps, using PyInstaller to build first simple, and then more complex PySide6 applications into distributable macOS app bundles. You can choose to follow it through completely, or skip to the parts that are most relevant to your own project.

We finish off by building a macOS Disk Image, the usual method for distributing applications on macOS.

You always need to compile your app on your target system. So, if you want to create a Mac .app you need to do this on a Mac, for an EXE you need to use Windows.

Example Disk Image Installer for macOS

If you're impatient, you can download the Example Disk Image for macOS first.


PyInstaller works out of the box with PySide6 and as of writing, current versions of PyInstaller are compatible with Python 3.6+. Whatever project you're working on, you should be able to package your apps.

You can install PyInstaller using pip.

bash pip3 install PyInstaller

If you experience problems packaging your apps, your first step should always be to update your PyInstaller and hooks package the latest versions using

bash pip3 install --upgrade PyInstaller pyinstaller-hooks-contrib

The hooks module contains package-specific packaging instructions for PyInstaller which is updated regularly.

Install in virtual environment (optional)

You can also opt to install PySide6 and PyInstaller in a virtual environment (or your applications virtual environment) to keep your environment clean.

bash python3 -m venv packenv

Once created, activate the virtual environment by running from the command line —

bash call packenv\scripts\activate.bat

Finally, install the required libraries. For PySide6 you would use —

python pip3 install PySide6 PyInstaller Getting Started

It's a good idea to start packaging your application from the very beginning so you can confirm that packaging is still working as you develop it. This is particularly important if you add additional dependencies. If you only think about packaging at the end, it can be difficult to debug exactly where the problems are.

For this example we're going to start with a simple skeleton app, which doesn't do anything interesting. Once we've got the basic packaging process working, we'll extend the application to include icons and data files. We'll confirm the build as we go along.

To start with, create a new folder for your application and then add the following skeleton app in a file named app.py. You can also download the source code and associated files

python from PySide6 import QtWidgets import sys class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Hello World") l = QtWidgets.QLabel("My simple app.") l.setMargin(10) self.setCentralWidget(l) self.show() if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) w = MainWindow() app.exec()

This is a basic bare-bones application which creates a custom QMainWindow and adds a simple widget QLabel to it. You can run this app as follows.

bash python app.py

This should produce the following window (on macOS).

Simple skeleton app in PySide6

Building a basic app

Now we have our simple application skeleton in place, we can run our first build test to make sure everything is working.

Open your terminal (command prompt) and navigate to the folder containing your project. You can now run the following command to run the PyInstaller build.

python pyinstaller --windowed app.py

The --windowed flag is necessary to tell PyInstaller to build a macOS .app bundle.

You'll see a number of messages output, giving debug information about what PyInstaller is doing. These are useful for debugging issues in your build, but can otherwise be ignored. The output that I get for running the command on my system is shown below.

bash martin@MacBook-Pro pyside6 % pyinstaller --windowed app.py 74 INFO: PyInstaller: 4.8 74 INFO: Python: 3.9.9 83 INFO: Platform: macOS-10.15.7-x86_64-i386-64bit 84 INFO: wrote /Users/martin/app/pyside6/app.spec 87 INFO: UPX is not available. 88 INFO: Extending PYTHONPATH with paths ['/Users/martin/app/pyside6'] 447 INFO: checking Analysis 451 INFO: Building because inputs changed 452 INFO: Initializing module dependency graph... 455 INFO: Caching module graph hooks... 463 INFO: Analyzing base_library.zip ... 3914 INFO: Processing pre-find module path hook distutils from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks/pre_find_module_path/hook-distutils.py'. 3917 INFO: distutils: retargeting to non-venv dir '/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9' 6928 INFO: Caching module dependency graph... 7083 INFO: running Analysis Analysis-00.toc 7091 INFO: Analyzing /Users/martin/app/pyside6/app.py 7138 INFO: Processing module hooks... 7139 INFO: Loading module hook 'hook-PyQt6.QtWidgets.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7336 INFO: Loading module hook 'hook-xml.etree.cElementTree.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7337 INFO: Loading module hook 'hook-lib2to3.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7360 INFO: Loading module hook 'hook-PyQt6.QtGui.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7397 INFO: Loading module hook 'hook-PyQt6.QtCore.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7422 INFO: Loading module hook 'hook-encodings.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7510 INFO: Loading module hook 'hook-distutils.util.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7513 INFO: Loading module hook 'hook-pickle.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7515 INFO: Loading module hook 'hook-heapq.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7517 INFO: Loading module hook 'hook-difflib.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7519 INFO: Loading module hook 'hook-PyQt6.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7564 INFO: Loading module hook 'hook-multiprocessing.util.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7565 INFO: Loading module hook 'hook-sysconfig.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7574 INFO: Loading module hook 'hook-xml.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7677 INFO: Loading module hook 'hook-distutils.py' from '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks'... 7694 INFO: Looking for ctypes DLLs 7712 INFO: Analyzing run-time hooks ... 7715 INFO: Including run-time hook '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_subprocess.py' 7719 INFO: Including run-time hook '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_pkgutil.py' 7722 INFO: Including run-time hook '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_multiprocessing.py' 7726 INFO: Including run-time hook '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_inspect.py' 7727 INFO: Including run-time hook '/usr/local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_pyqt6.py' 7736 INFO: Looking for dynamic libraries 7977 INFO: Looking for eggs 7977 INFO: Using Python library /usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/Python 7987 INFO: Warnings written to /Users/martin/app/pyside6/build/app/warn-app.txt 8019 INFO: Graph cross-reference written to /Users/martin/app/pyside6/build/app/xref-app.html 8032 INFO: checking PYZ 8035 INFO: Building because toc changed 8035 INFO: Building PYZ (ZlibArchive) /Users/martin/app/pyside6/build/app/PYZ-00.pyz 8390 INFO: Building PYZ (ZlibArchive) /Users/martin/app/pyside6/build/app/PYZ-00.pyz completed successfully. 8397 INFO: EXE target arch: x86_64 8397 INFO: Code signing identity: None 8398 INFO: checking PKG 8398 INFO: Building because /Users/martin/app/pyside6/build/app/PYZ-00.pyz changed 8398 INFO: Building PKG (CArchive) app.pkg 8415 INFO: Building PKG (CArchive) app.pkg completed successfully. 8417 INFO: Bootloader /usr/local/lib/python3.9/site-packages/PyInstaller/bootloader/Darwin-64bit/runw 8417 INFO: checking EXE 8418 INFO: Building because console changed 8418 INFO: Building EXE from EXE-00.toc 8418 INFO: Copying bootloader EXE to /Users/martin/app/pyside6/build/app/app 8421 INFO: Converting EXE to target arch (x86_64) 8449 INFO: Removing signature(s) from EXE 8484 INFO: Appending PKG archive to EXE 8486 INFO: Fixing EXE headers for code signing 8496 INFO: Rewriting the executable's macOS SDK version (11.1.0) to match the SDK version of the Python library (10.15.6) in order to avoid inconsistent behavior and potential UI issues in the frozen application. 8499 INFO: Re-signing the EXE 8547 INFO: Building EXE from EXE-00.toc completed successfully. 8549 INFO: checking COLLECT WARNING: The output directory "/Users/martin/app/pyside6/dist/app" and ALL ITS CONTENTS will be REMOVED! Continue? (y/N)y On your own risk, you can use the option `--noconfirm` to get rid of this question. 10820 INFO: Removing dir /Users/martin/app/pyside6/dist/app 10847 INFO: Building COLLECT COLLECT-00.toc 12460 INFO: Building COLLECT COLLECT-00.toc completed successfully. 12469 INFO: checking BUNDLE 12469 INFO: Building BUNDLE because BUNDLE-00.toc is non existent 12469 INFO: Building BUNDLE BUNDLE-00.toc 13848 INFO: Moving BUNDLE data files to Resource directory 13901 INFO: Signing the BUNDLE... 16049 INFO: Building BUNDLE BUNDLE-00.toc completed successfully.

If you look in your folder you'll notice you now have two new folders dist and build.

build & dist folders created by PyInstaller

Below is a truncated listing of the folder content, showing the build and dist folders.

bash . &boxvr&boxh&boxh app.py &boxvr&boxh&boxh app.spec &boxvr&boxh&boxh build &boxv   &boxur&boxh&boxh app &boxv   &boxvr&boxh&boxh Analysis-00.toc &boxv   &boxvr&boxh&boxh COLLECT-00.toc &boxv   &boxvr&boxh&boxh EXE-00.toc &boxv   &boxvr&boxh&boxh PKG-00.pkg &boxv   &boxvr&boxh&boxh PKG-00.toc &boxv   &boxvr&boxh&boxh PYZ-00.pyz &boxv   &boxvr&boxh&boxh PYZ-00.toc &boxv   &boxvr&boxh&boxh app &boxv   &boxvr&boxh&boxh app.pkg &boxv   &boxvr&boxh&boxh base_library.zip &boxv   &boxvr&boxh&boxh warn-app.txt &boxv   &boxur&boxh&boxh xref-app.html &boxur&boxh&boxh dist &boxvr&boxh&boxh app &boxv &boxvr&boxh&boxh libcrypto.1.1.dylib &boxv &boxvr&boxh&boxh PySide6 &boxv ... &boxv &boxvr&boxh&boxh app &boxv &boxur&boxh&boxh Qt5Core &boxur&boxh&boxh app.app

The build folder is used by PyInstaller to collect and prepare the files for bundling, it contains the results of analysis and some additional logs. For the most part, you can ignore the contents of this folder, unless you're trying to debug issues.

The dist (for "distribution") folder contains the files to be distributed. This includes your application, bundled as an executable file, together with any associated libraries (for example PySide6) and binary .so files.

Since we provided the --windowed flag above, PyInstaller has actually created two builds for us. The folder app is a simple folder containing everything you need to be able to run your app. PyInstaller also creates an app bundle app.app which is what you will usually distribute to users.

The app folder is a useful debugging tool, since you can easily see the libraries and other packaged data files.

You can try running your app yourself now, either by double-clicking on the app bundle, or by running the executable file, named app.exe from the dist folder. In either case, after a short delay you'll see the familiar window of your application pop up as shown below.

Simple app, running after being packaged

In the same folder as your Python file, alongside the build and dist folders PyInstaller will have also created a .spec file. In the next section we'll take a look at this file, what it is and what it does.

The Spec file

The .spec file contains the build configuration and instructions that PyInstaller uses to package up your application. Every PyInstaller project has a .spec file, which is generated based on the command line options you pass when running pyinstaller.

When we ran pyinstaller with our script, we didn't pass in anything other than the name of our Python application file and the --windowed flag. This means our spec file currently contains only the default configuration. If you open it, you'll see something similar to what we have below.

python # -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis(['app.py'], pathex=[], binaries=[], datas=[], hiddenimports=[], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, a.scripts, [], exclude_binaries=True, name='app', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=False, disable_windowed_traceback=False, target_arch=None, codesign_identity=None, entitlements_file=None ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='app') app = BUNDLE(coll, name='app.app', icon=None, bundle_identifier=None)

The first thing to notice is that this is a Python file, meaning you can edit it and use Python code to calculate values for the settings. This is mostly useful for complex builds, for example when you are targeting different platforms and want to conditionally define additional libraries or dependencies to bundle.

Because we used the --windowed command line flag, the EXE(console=) attribute is set to False. If this is True a console window will be shown when your app is launched -- not what you usually want for a GUI application.

Once a .spec file has been generated, you can pass this to pyinstaller instead of your script to repeat the previous build process. Run this now to rebuild your executable.

bash pyinstaller app.spec

The resulting build will be identical to the build used to generate the .spec file (assuming you have made no changes). For many PyInstaller configuration changes you have the option of passing command-line arguments, or modifying your existing .spec file. Which you choose is up to you.

Tweaking the build

So far we've created a simple first build of a very basic application. Now we'll look at a few of the most useful options that PyInstaller provides to tweak our build. Then we'll go on to look at building more complex applications.

Naming your app

One of the simplest changes you can make is to provide a proper "name" for your application. By default the app takes the name of your source file (minus the extension), for example main or app. This isn't usually what you want.

You can provide a nicer name for PyInstaller to use for the app (and dist folder) by editing the .spec file to add a name= under the EXE, COLLECT and BUNDLE blocks.

python exe = EXE(pyz, a.scripts, [], exclude_binaries=True, name='Hello World', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=False ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='Hello World') app = BUNDLE(coll, name='Hello World.app', icon=None, bundle_identifier=None)

The name under EXE is the name of the executable file, the name under BUNDLE is the name of the app bundle.

Alternatively, you can re-run the pyinstaller command and pass the -n or --name configuration flag along with your app.py script.

bash pyinstaller -n "Hello World" --windowed app.py # or pyinstaller --name "Hello World" --windowed app.py

The resulting app file will be given the name Hello World.app and the unpacked build placed in the folder dist\Hello World\.

Application with custom name "Hello World"

The name of the .spec file is taken from the name passed in on the command line, so this will also create a new spec file for you, called Hello World.spec in your root folder.

Make sure you delete the old app.spec file to avoid getting confused editing the wrong one.

Application icon

By default PyInstaller app bundles come with the following icon in place.

Default PyInstaller application icon, on app bundle

You will probably want to customize this to make your application more recognisable. This can be done easily by passing the --icon command line argument, or editing the icon= parameter of the BUNDLE section of your .spec file. For macOS app bundles you need to provide an .icns file.

python app = BUNDLE(coll, name='Hello World.app', icon='Hello World.icns', bundle_identifier=None)

To create macOS icons from images you can use the image2icon tool.

If you now re-run the build (by using the command line arguments, or running with your modified .spec file) you'll see the specified icon file is now set on your application bundle.

Custom application icon on the app bundle

On macOS application icons are taken from the application bundle. If you repackage your app and run the bundle you will see your app icon on the dock!

Custom application icon on the dock

Data files and Resources

So far our application consists of just a single Python file, with no dependencies. Most real-world applications a bit more complex, and typically ship with associated data files such as icons or UI design files. In this section we'll look at how we can accomplish this with PyInstaller, starting with a single file and then bundling complete folders of resources.

First let's update our app with some more buttons and add icons to each.

python from PySide6.QtWidgets import QMainWindow, QApplication, QLabel, QVBoxLayout, QPushButton, QWidget from PySide6.QtGui import QIcon import sys class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Hello World") layout = QVBoxLayout() label = QLabel("My simple app.") label.setMargin(10) layout.addWidget(label) button1 = QPushButton("Hide") button1.setIcon(QIcon("icons/hand.png")) button1.pressed.connect(self.lower) layout.addWidget(button1) button2 = QPushButton("Close") button2.setIcon(QIcon("icons/lightning.png")) button2.pressed.connect(self.close) layout.addWidget(button2) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) self.show() if __name__ == '__main__': app = QApplication(sys.argv) w = MainWindow() app.exec_()

In the folder with this script, add a folder icons which contains two icons in PNG format, hand.png and lightning.png. You can create these yourself, or get them from the source code download for this tutorial.

Run the script now and you will see a window showing two buttons with icons.

Window with two buttons with icons.

Even if you don't see the icons, keep reading!

Dealing with relative paths

There is a gotcha here, which might not be immediately apparent. To demonstrate it, open up a shell and change to the folder where our script is located. Run it with

bash python3 app.py

If the icons are in the correct location, you should see them. Now change to the parent folder, and try and run your script again (change <folder> to the name of the folder your script is in).

bash cd .. python3 <folder>/app.py

Window with two buttons with icons missing.

The icons don't appear. What's happening?

We're using relative paths to refer to our data files. These paths are relative to the current working directory -- not the folder your script is in. So if you run the script from elsewhere it won't be able to find the files.

One common reason for icons not to show up, is running examples in an IDE which uses the project root as the current working directory.

This is a minor issue before the app is packaged, but once it's installed it will be started with it's current working directory as the root / folder -- your app won't be able to find anything. We need to fix this before we go any further, which we can do by making our paths relative to our application folder.

In the updated code below, we define a new variable basedir, using os.path.dirname to get the containing folder of __file__ which holds the full path of the current Python file. We then use this to build the relative paths for icons using os.path.join().

Since our app.py file is in the root of our folder, all other paths are relative to that.

python from PySide6.QtWidgets import QMainWindow, QApplication, QLabel, QVBoxLayout, QPushButton, QWidget from PySide6.QtGui import QIcon import sys, os basedir = os.path.dirname(__file__) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Hello World") layout = QVBoxLayout() label = QLabel("My simple app.") label.setMargin(10) layout.addWidget(label) button1 = QPushButton("Hide") button1.setIcon(QIcon(os.path.join(basedir, "icons", "hand.png"))) button1.pressed.connect(self.lower) layout.addWidget(button1) button2 = QPushButton("Close") button2.setIcon(QIcon(os.path.join(basedir, "icons", "lightning.png"))) button2.pressed.connect(self.close) layout.addWidget(button2) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) self.show() if __name__ == '__main__': app = QApplication(sys.argv) w = MainWindow() app.exec_()

Try and run your app again from the parent folder -- you'll find that the icons now appear as expected on the buttons, no matter where you launch the app from.

Packaging the icons

So now we have our application showing icons, and they work wherever the application is launched from. Package the application again with pyinstaller "Hello World.spec" and then try and run it again from the dist folder as before. You'll notice the icons are missing again.

Window with two buttons with icons missing.

The problem now is that the icons haven't been copied to the dist/Hello World folder -- take a look in it. Our script expects the icons to be a specific location relative to it, and if they are not, then nothing will be shown.

This same principle applies to any other data files you package with your application, including Qt Designer UI files, settings files or source data. You need to ensure that relative path structures are replicated after packaging.

Bundling data files with PyInstaller

For the application to continue working after packaging, the files it depends on need to be in the same relative locations.

To get data files into the dist folder we can instruct PyInstaller to copy them over. PyInstaller accepts a list of individual paths to copy, together with a folder path relative to the dist/<app name> folder where it should to copy them to. As with other options, this can be specified by command line arguments or in the .spec file.

Files specified on the command line are added using --add-data, passing the source file and destination folder separated by a colon :.

The path separator is platform-specific: Linux or Mac use :, on Windows use ;

bash pyinstaller --windowed --name="Hello World" --icon="Hello World.icns" --add-data="icons/hand.png:icons" --add-data="icons/lightning.png:icons" app.py

Here we've specified the destination location as icons. The path is relative to the root of our application's folder in dist -- so dist/Hello World with our current app. The path icons means a folder named icons under this location, so dist/Hello World/icons. Putting our icons right where our application expects to find them!

You can also specify data files via the datas list in the Analysis section of the spec file, shown below.

python a = Analysis(['app.py'], pathex=[], binaries=[], datas=[('icons/hand.png', 'icons'), ('icons/lightning.png', 'icons')], hiddenimports=[], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False)

Then rebuild from the .spec file with

bash pyinstaller "Hello World.spec"

In both cases we are telling PyInstaller to copy the specified files to the location ./icons/ in the output folder, meaning dist/Hello World/icons. If you run the build, you should see your .png files are now in the in dist output folder, under a folder named icons.

The icon file copied to the dist folder

If you run your app from dist you should now see the icon icons in your window as expected!

Window with two buttons with icons, finally!

Bundling data folders

Usually you will have more than one data file you want to include with your packaged file. The latest PyInstaller versions let you bundle folders just like you would files, keeping the sub-folder structure.

Let's update our configuration to bundle our icons folder in one go, so it will continue to work even if we add more icons in future.

To copy the icons folder across to our build application, we just need to add the folder to our .spec file Analysis block. As for the single file, we add it as a tuple with the source path (from our project folder) and the destination folder under the resulting folder in dist.

python # ... a = Analysis(['app.py'], pathex=[], binaries=[], datas=[('icons', 'icons')], # tuple is (source_folder, destination_folder) hiddenimports=[], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False) # ...

If you run the build using this spec file you'll see the icons folder copied across to the dist\Hello World folder. If you run the application from the folder, the icons will display as expected -- the relative paths remain correct in the new location.

Alternatively, you can bundle your data files using Qt's QResource architecture. See our tutorial for more information.

Building the App bundle into a Disk Image

So far we've used PyInstaller to bundle the application into macOS app, along with the associated data files. The output of this bundling process is a folder and an macOS app bundle, named Hello World.app.

If you try and distribute this app bundle, you'll notice a problem: the app bundle is actually just a special folder. While macOS displays it as an application, if you try and share it, you'll actually be sharing hundreds of individual files. To distribute the app properly, we need some way to package it into a single file.

The easiest way to do this is to use a .zip file. You can zip the folder and give this to someone else to unzip on their own computer, giving them a complete app bundle they can copy to their Applications folder.

However, if you've install macOS applications before you'll know this isn't the usual way to do it. Usually you get a Disk Image .dmg file, which when opened shows the application bundle, and a link to your Applications folder. To install the app, you just drag it across to the target.

To make our app look as professional as possible, we should copy this expected behaviour. Next we'll look at how to take our app bundle and package it into a macOS Disk Image.

Making sure the build is ready.

If you've followed the tutorial so far, you'll already have your app ready in the /dist folder. If not, or yours isn't working you can also download the source code files for this tutorial which includes a sample .spec file. As above, you can run the same build using the provided Hello World.spec file.

bash pyinstaller "Hello World.spec"

This packages everything up as an app bundle in the dist/ folder, with a custom icon. Run the app bundle to ensure everything is bundled correctly, and you should see the same window as before with the icons visible.

Window with two icons, and a button.

Creating an Disk Image

Now we've successfully bundled our application, we'll next look at how we can take our app bundle and use it to create a macOS Disk Image for distribution.

To create our Disk Image we'll be using the create-dmg tool. This is a command-line tool which provides a simple way to build disk images automatically. If you are using Homebrew, you can install create-dmg with the following command.

bash brew install create-dmg

...otherwise, see the Github repository for instructions.

The create-dmg tool takes a lot of options, but below are the most useful.

bash create-dmg --help create-dmg 1.0.9 Creates a fancy DMG file. Usage: create-dmg [options] <output_name.dmg> <source_folder> All contents of <source_folder> will be copied into the disk image. Options: --volname <name> set volume name (displayed in the Finder sidebar and window title) --volicon <icon.icns> set volume icon --background <pic.png> set folder background image (provide png, gif, or jpg) --window-pos <x> <y> set position the folder window --window-size <width> <height> set size of the folder window --text-size <text_size> set window text size (10-16) --icon-size <icon_size> set window icons size (up to 128) --icon file_name <x> <y> set position of the file's icon --hide-extension <file_name> hide the extension of file --app-drop-link <x> <y> make a drop link to Applications, at location x,y --no-internet-enable disable automatic mount & copy --add-file <target_name> <file>|<folder> <x> <y> add additional file or folder (can be used multiple times) -h, --help display this help screen

The most important thing to notice is that the command requires a <source folder> and all contents of that folder will be copied to the Disk Image. So to build the image, we first need to put our app bundle in a folder by itself.

Rather than do this manually each time you want to build a Disk Image I recommend creating a shell script. This ensures the build is reproducible, and makes it easier to configure.

Below is a working script to create a Disk Image from our app. It creates a temporary folder dist/dmg where we'll put the things we want to go in the Disk Image -- in our case, this is just the app bundle, but you can add other files if you like. Then we make sure the folder is empty (in case it still contains files from a previous run). We copy our app bundle into the folder, and finally check to see if there is already a .dmg file in dist and if so, remove it too. Then we're ready to run the create-dmg tool.

bash #!/bin/sh # Create a folder (named dmg) to prepare our DMG in (if it doesn't already exist). mkdir -p dist/dmg # Empty the dmg folder. rm -r dist/dmg/* # Copy the app bundle to the dmg folder. cp -r "dist/Hello World.app" dist/dmg # If the DMG already exists, delete it. test -f "dist/Hello World.dmg" && rm "dist/Hello World.dmg" create-dmg \ --volname "Hello World" \ --volicon "Hello World.icns" \ --window-pos 200 120 \ --window-size 600 300 \ --icon-size 100 \ --icon "Hello World.app" 175 120 \ --hide-extension "Hello World.app" \ --app-drop-link 425 120 \ "dist/Hello World.dmg" \ "dist/dmg/"

The options we pass to create-dmg set the dimensions of the Disk Image window when it is opened, and positions of the icons in it.

Save this shell script in the root of your project, named e.g. builddmg.sh. To make it possible to run, you need to set the execute bit with.

bash chmod +x builddmg.sh

With that, you can now build a Disk Image for your Hello World app with the command.

bash ./builddmg.sh

This will take a few seconds to run, producing quite a bit of output.

bash No such file or directory Creating disk image... ............................................................... created: /Users/martin/app/dist/rw.Hello World.dmg Mounting disk image... Mount directory: /Volumes/Hello World Device name: /dev/disk2 Making link to Applications dir... /Volumes/Hello World Copying volume icon file 'Hello World.icns'... Running AppleScript to make Finder stuff pretty: /usr/bin/osascript "/var/folders/yf/1qvxtg4d0vz6h2y4czd69tf40000gn/T/createdmg.tmp.XXXXXXXXXX.RvPoqdr0" "Hello World" waited 1 seconds for .DS_STORE to be created. Done running the AppleScript... Fixing permissions... Done fixing permissions Blessing started Blessing finished Deleting .fseventsd Unmounting disk image... hdiutil: couldn't unmount "disk2" - Resource busy Wait a moment... Unmounting disk image... "disk2" ejected. Compressing disk image... Preparing imaging engine… Reading Protective Master Boot Record (MBR : 0)… (CRC32 $38FC6E30: Protective Master Boot Record (MBR : 0)) Reading GPT Header (Primary GPT Header : 1)… (CRC32 $59C36109: GPT Header (Primary GPT Header : 1)) Reading GPT Partition Data (Primary GPT Table : 2)… (CRC32 $528491DC: GPT Partition Data (Primary GPT Table : 2)) Reading (Apple_Free : 3)… (CRC32 $00000000: (Apple_Free : 3)) Reading disk image (Apple_HFS : 4)… ............................................................................... (CRC32 $FCDC1017: disk image (Apple_HFS : 4)) Reading (Apple_Free : 5)… ............................................................................... (CRC32 $00000000: (Apple_Free : 5)) Reading GPT Partition Data (Backup GPT Table : 6)… ............................................................................... (CRC32 $528491DC: GPT Partition Data (Backup GPT Table : 6)) Reading GPT Header (Backup GPT Header : 7)… ............................................................................... (CRC32 $56306308: GPT Header (Backup GPT Header : 7)) Adding resources… ............................................................................... Elapsed Time: 3.443s File size: 23178950 bytes, Checksum: CRC32 $141F3DDC Sectors processed: 184400, 131460 compressed Speed: 18.6Mbytes/sec Savings: 75.4% created: /Users/martin/app/dist/Hello World.dmg hdiutil does not support internet-enable. Note it was removed in macOS 10.15. Disk image done

While it's building, the Disk Image will pop up. Don't get too excited yet, it's still building. Wait for the script to complete, and you will find the finished .dmg file in the dist/ folder.

The Disk Image created in the dist folder

Running the installer

Double-click the Disk Image to open it, and you'll see the usual macOS install view. Click and drag your app across the the Applications folder to install it.

The Disk Image contains the app bundle and a shortcut to the applications folder

If you open the Showcase view (press F4) you will see your app installed. If you have a lot of apps, you can search for it by typing "Hello"

The app installed on macOS

Repeating the build

Now you have everything set up, you can create a new app bundle & Disk Image of your application any time, by running the two commands from the command line.

bash pyinstaller "Hello World.spec" ./builddmg.sh

It's that simple!

Wrapping up

In this tutorial we've covered how to build your PySide6 applications into a macOS app bundle using PyInstaller, including adding data files along with your code. Then we walked through the process of creating a Disk Image to distribute your app to others. Following these steps you should be able to package up your own applications and make them available to other people.

For a complete view of all PyInstaller bundling options take a look at the PyInstaller usage documentation.

For an in-depth guide to building GUIs with Python see my PyQt6 book.

Categories: FLOSS Project Planets

Paul Wise: FLOSS Activities April 2022

Planet Debian - Sat, 2022-04-30 20:26

This month I didn't have any particular focus. I just worked on issues in my info bubble.

Changes Issues Review
  • Spam: reported 33 Debian mailing list posts
  • Debian wiki: RecentChanges for the month
  • Debian BTS usertags: changes for the month
  • Debian screenshots:
  • Debian wiki: unblock IP addresses, approve accounts
Communication Sponsors

The libpst, gensim, SPTAG work was sponsored. All other work was done on a volunteer basis.

Categories: FLOSS Project Planets

Junichi Uekawa: Already May.

Planet Debian - Sat, 2022-04-30 19:57
Already May. I've been writing some code in rust and a bit of javascript. But real life is too busy.

Categories: FLOSS Project Planets

Linux App Summit 2022 - Day 1

Planet KDE - Sat, 2022-04-30 16:39

00:11:00 Opening Remarks
00:32:00 Q/A and with Neil McGovern (GNOME) and Aleix Pol (KDE)
00:39:00 Quality over Quantity; Best Practices for an Effective Open Source Contribution - Ruth Ikegah
00:55:00 Flathub: now and next - Jorge Castro and Robert McQueen
01:55:00 Linux: a viable platform for media art - Jean-Michaël Celerier
02:31:45 Fractal-next: The journey of rewriting a GTK Matrix client - Julian Sparber
04:18:00 Canonical Office Hour - 22.04 Virtual Release Party - Britt Yazel, Heather Ellsworth, Igor Ljubuncic, Monica Ayhens-Madon and Till Kamppeter
04:58:10 Designing for Open Source Applications - Rajdeep Singha
05:36:50 Snapcraft Secrets: The hidden power of desktop extensions - Igor Ljubuncic
06:31:20 Getting Good: Gaming on Linux - Heather Ellsworth
07:14:30 Integrating Progressive Web Apps in GNOME - Phaedrus Leeds
07:51:20 A new distribution openSUSE Leap Micro - Doug DeMaio
08:00:00 Your app on the Steamdeck - Aleix Pol
08:13:00 Funding Flatpak Development on Open Collective - Phaedrus Leeds
08:21:00 Pro Audio on Linux in 2022 - Sam Thursfield
08:32:00 Make Computing Personal Again! - Parke Bostrom
08:42:00 Screen sharing in online meetings on Wayland - Jan Grulich
08:51:20 The Ubuntu Summit in Prague - OR - The return of UDS! - Till Kamppeter

Categories: FLOSS Project Planets

William Minchin: Static Comments Plugin 2.1.1 for Pelican Released

Planet Python - Sat, 2022-04-30 16:17

Static Comments is a plugin for Pelican, a static site generator written in Python. It is meant as a drop in replacement for the Pelican Comment System.

Static Comments allows you to have a comment section on your Pelican blog, while maintaining your blog as a completely static webpage and without relying on any external services or servers; just an email address is required. Comments are stored as text files, similiar in structure to Pelican articles. This gives you complete control over the comments appearing on your site and allows you to back them up with the rest of your site.

This Release

This release takes the existing Pelican Comment System codebase and upgrades it to work with Pelican 4 (and should continue to work with Pelican 3). A few changes are needed in your configuration, but no changes to your comments files should be needed.


The simplest way to install the Python code of Static Comments is to use pip:

pip install minchin.pelican.plugins.static-comments --upgrade

If you are using Pelican 4.5+, the plugin will automatally be loaded (although not activated).

If you are an earlier version of Pelican, or non-namespace plugins, you will need to add the auto-loader to your list of plugins:

# pelicanconf.py PLUGINS = [ # others "minchin.pelican.plugins.autoloader", ]

Activate the plugin by adding the following line to your pelicanconf.py:

# pelicanconf.py PELICAN_COMMENT_SYSTEM = True

and then set the email you want to receive comment emails at:


Finally, modify the article.html of your theme (if your theme doesn’t support Static Comments out of the box) to both display comments already submitted and to have a comment submission form. The sample submission form works by using JavaScript to convert the form contents (the commenter’s name, site, and comment body) to an email the user then sends to you. Note, this is an example of the code you might use for your theme, but please feel free to modify it to suit your needs.

{% macro comments_styles() %} {% if PELICAN_COMMENT_SYSTEM %} {# NOTE: # Instead of using this macro copy these styles in your main css file # This marco is only here to allow a quickstart with nice styles #} #pcs-comment-form input, #pcs-comment-form textarea { width: 100%; } #pcs-comment-form-display-replyto { border: solid 1px black; padding: 2px; } #pcs-comment-form-display-replyto p { margin-top: 0.5em; margin-bottom: 0.5em; } #pcs-comments ul { list-style: none; } #pcs-comments .comment-left { display: table-cell; padding-right: 10px; } #pcs-comments .comment-body { display: table-cell; vertical-align: top; width: 100%; } {% endif %} {% endmacro %} {% macro comments_form() %} {% if PELICAN_COMMENT_SYSTEM %} Add a Comment Name Website Your Comment

You can use the Markdown syntax to format your comment.

="" span="" type="submit"> id="pcs-comment-form-button-submit" {# Piwik Track click on comment button onclick="javascript:_paq.push(['trackEvent', 'comment', '{{ article.title }}', document.getElementById('pcs-comment-form-input-textarea').value]);" #} >Post via email {% if PELICAN_COMMENT_SYSTEM_FEED and article %} Comment Atom Feed {% endif %} {% endif %} {% endmacro %} {% macro comments_with_form() %} {% if PELICAN_COMMENT_SYSTEM %} Comments {% if article.comments %}
    {% for comment in article.comments recursive %}
  • ="" span="" src="{{ SITEURL }}/{{ comment.avatar }}"/> alt="Avatar" height="{{ PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE }}" width="{{ PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE }}"> Permalink Reply {% if comment.metadata['website'] %} {{ comment.author }} {% else %} {{ comment.author }} {% endif %}

    Posted on {{ comment.locale_date }}

    {{ comment.content }} {% if comment.replies %}
      {{ loop(comment.replies) }}
    {% endif %}
  • {% endfor %}
{% else %}

There are no comments yet.

{% endif %} {{ comments_form() }} {% endif %} {% endmacro %} {% macro comments_js(user, domain, includeJquery=True) %} {% if PELICAN_COMMENT_SYSTEM %} {% if includeJquery %} {% endif %}


+++ {% endif %} {% endmacro %} {% macro comments_quickstart(user, domain) %} {{ comments_styles() }} {{ comments_with_form() }} {{ comments_js(user, domain) }} {% endmacro %} What A Comment File Looks Like

When a user submits a comment, you will get an email with the details. You then take those details from your email and create a text file within your Pelican site, one for each comment. By default, the plugin will look for comments in a folder comments in your root content folder (probably the same one your have your Pelican articles in), and then in subfolders that match the slug of the article the comment applies to.

The actual comment file will look something like this:

email: noreplay@blogger.com date: 2019-07-15T12:20+01:00 author: Mahassine replyto: comment-slug-2382md Sample comment body.

The replyto tag is only needed if this comment is indeed a reply to another comment. The value of the replyto tag is the slug of the comment, which is the filename plus the file extension, but not the period between them.

The comment files can be in any format Pelican is set up to read (typically Markdown and ReStructed Text, but many others supported).

I realize that this is fairly involved to activate as far as Pelican plugins go, so if you run into issues, please leave a comment on this post!

Upgrading (from the Pelican Comment System)

Upgrading from the Pelican Comment System should be seemless, and should be as simple as uninstalling the Pelican Comment System (and removing it from your pelicanconf.py) and installing Static Comments.

pip uninstall pelican-comment-system pip install minchin.pelican.plugins.static-comments --upgrade

Existing comments files should work out of the box, and the setting haven’t been renamed.

Known Issues
  • To get this to work, you’ll need to update your theme (or find one, like Seafoam, that works out of the box). I appologize for how involved this is, but Pelican doesn’t have any framework for editing themes from plugins.
  • The current setup requires JavaScript, and there probably isn’t a great “no script” solution. As an alternative, you can ask people to just email you their comments directly and offer to post them. I actually do a version of this on my Genealogy site and get a rather consistent stream of emails, so it may be a viable option.
  • You have to manually add comments to your site, regenerate it, and reupload it before the comments will show on your site. This means that comments won’t immediately show, and the the workflow may not be feasible if you have a high comment volume.
  • The documentation that is on the Github repo has been reviewed, but may still be out of date in places. Please let me know if you notice any issues there.
  • If you are moving your site to Pelican from another system, importing your existing comments can be rather involved. There is an included script for Blogger comments that I used myself, but because such imports tend to be one-offs, these scripts don’t get checked that often and so could break if the export format changes. If you have a significant volume of comments you want to import, you may have to write your own script (which I’d be happy to include with the plugin if you send me a pull request!).
Future Plans

At this point, the plugin seems feature complete. I expect future changes will be about fixing code errors or to keep it working as Pelican progresses.

Personal Thoughts

I’m excited to get this updated and out into the world. I’m a little sad that the old Pelican Comment System seems to be no longer being updated, although it looks like it got stuck halfway through a “version 2” complete rewrite, so add this as another warning about ground up rewrites. But this is the wonder of Open Source: I can take the existing codebase, fix the errors and issues, and release a new working version back into the world.

There is also a more general question of whether comments are worth keeping around. Considering that you’re reading this, and I released this plugin, I think we are both in agreement that the answer is “yes”. I have definitely seen a the number of comments posted to my blog drop over the years (this blog has been up since 2006!), but I suspect that is mostly tied to lower traffic volumes. As for comments generally, Twitter and Reddit I think provides proof that people still want to add their two cents on things, and personally, I would rather have the conversion here rather than on another site (like Reddit) that I don’t control and have the ability to backup that conversation.

Overall, I’m pretty satisfied with this solution. The biggest downside is that comments don’t post automatically and so can take some time to show as I have to manually post them, but I think that tradeoff is worth not having to maintain a separate server just for commenting.

As with all my plugins, if the pelican-plugins group wants to adopt these, I’d be happy to have the community support there.

Categories: FLOSS Project Planets

STX Next: Python vs. C++: A Comparison of Key Features and Differences

Planet Python - Sat, 2022-04-30 14:26

C++ and Python, two of the most popular and commonly used programming languages, aren’t only versatile and object-oriented, but they can be used to create a wide array of various programs and functional code.

Categories: FLOSS Project Planets

Linux App Summit 2022 - Day 2

Planet KDE - Sat, 2022-04-30 03:04

Linux App Summit 2022 - Day 2

Categories: FLOSS Project Planets

Zero to Mastery: Python Monthly Newsletter 💻🐍

Planet Python - Sat, 2022-04-30 00:41
29th issue of the Python Monthly Newsletter! Read by 20,000+ Python developers every month. This monthly Python newsletter covers the latest Python news so that you stay up-to-date with the industry and keep your skills sharp.
Categories: FLOSS Project Planets

This week in KDE: Porting everything to QtQuick

Planet KDE - Sat, 2022-04-30 00:37

Well OK not literally everything. But… a lot of things! The KWin scripts KCM, Filelight, and DrKonqi. What does this mean? From a user perspective, mostly not much except for better visuals that align them better with modern KDE UI design. But it also means better-separated internals, more modern code, and easier hackability for the UI. The software’s lifespan increases and we get closer to everything using the same tech stack. It’s important stuff.

In addition, you should find lots of nice bugfixes and even a few new features!

15-Minute Bugs Resolved

Current number of bugs: 70, down from 73. 2 added, 2 found to be duplicates of other 15-minute bugs, and 3 resolved:

Certain monitors no longer constantly power-cycle in a loop when connected (Xaver Hugl, Plasma 5.24.5)

Everyone can once again change their Favorites in Kickoff and Kicker and have those changes persist after restarting Plasma or the computer (Méven Car, Plasma 5.24.5)

After installing a Flatpak app using Discover, there’s no longer still a misleading “Install” button there anyway (Aleix Pol Gonzalez, Plasma 5.24.5)

Current list of bugs

New Features

Skanpage now supports exporting searchable PDFs using optical character recognition! (Alexander Stippich, Skanpage 22.08):

Dolphin now lets you sort by file extension if you prefer that (Eugene Popov, Dolphin 22.08)

In the Plasma Wayland session, you can now change the resolution of your screen to resolutions beyond the officially supported ones, just like you can in the X11 session (Xaver Hugl, Plasma 5.25)

Bugfixes & Performance Improvements

Dolphin’s Terminal Panel no longer gets de-synced from the view itself (Felix Ernst, Dolphin 22.04.1)

Elisa’s “Load Playlist…” and “Save Playlist…” actions now work from the global menu (Firlaev-Hans Fiete, Elisa 22.04.1)

Text in Filelight’s tooltips is no longer clipped away at the ends (Harald Sitter, Filelight 22.08)

Plasma no longer sometimes randomly crashes when you have more than one app with multiple windows open and you interact with one of their Task Manager tooltips (Fushan Wen, Plasma 5.24.5)

In the Plasma Wayland session, KWin no longer crashes when connected USB-C monitors wake up from their power-save states (Xaver Hugl, Plasma 5.24.5)

The Global Menu widget no longer shows menus that the app has marked as hidden, such as the “Tools” Menu in Kolourpaint (Kai Uwe Broulik, Plasma 5.24.5)

In the Plasma Wayland session, KWin no longer crashes when you close a laptop and re-open it when its internal screen is set to turn off when closed (Xaver Hugl, Plasma 5.25)

In the Plasma Wayland session, fixed another way that KWin could crash when you disconnect an external screen (Xaver Hugl, Plasma 5.25)

In an app using xdg-desktop-portals (e.g. sandboxed Flatpak and Snap apps), when you use a file dialog to access a file in a remote location that automatically gets mounted using kio-fuse under the hood, the next time you open the file dialog again, it will open showing the original location, not its weird-looking kio-fuse mountpoint (Harald Sitter, Plasma 5.25)

Closing a window that spawned a child “Get New [thing]” window now closes the child window too, rather than letting it keep living, and then the parent app either crashes or has an invisible window that can’t be shown again until you kill the app using System Monitor or a terminal window (Alexander Lohnau, Frameworks 5.94)

Apps like Konsole that allow you to set a custom color scheme for the whole window that overrides the system’s default color scheme are now substantially faster to launch (Nicolas Fella, Frameworks 5.94)

User Interface Improvements

The KWin Scripts KCM has been ported to QtQuick, modernizing its appearance and making future maintenance simpler (Alexander Lohnau, Plasma 5.25):

Filelight has been ported to QtQuick, modernizing its appearance and making future maintenance simpler (Harald Sitter, Filelight 22.08):

DrKonqi’s crash reporting wizard has been ported to QtQuick too! But honestly you should barely notice the difference (Harald Sitter, Plasma 5.25)

For apps using xdg-desktop-portals, the app chooser dialog now looks and behaves better (me: Nate Graham, Plasma 5.25):

For those of you who disliked the change to always skip minimized tasks when scrolling over the Task Manager to switch tasks, it’s now configurable (Abhijeet Viswa, Plasma 5.25)

…And everything else

This blog only covers the tip of the iceberg! Tons of KDE apps whose development I don’t have time to follow aren’t represented here, and I also don’t mention backend refactoring, improved test coverage, and other changes that are generally not user-facing. If you’re hungry for more, check out https://planet.kde.org, where you can find more news from other KDE contributors.

How You Can Help

If you’re a developer, check out our 15-Minute Bug Initiative. Working on these issues makes a big difference quickly!

Otherwise, have a look at https://community.kde.org/Get_Involved to discover ways to be part of a project that really matters. Each contributor makes a huge difference in KDE; you are not a number or a cog in a machine! You don’t have to already be a programmer, either. I wasn’t when I got started. Try it, you’ll like it! We don’t bite!

Finally, consider making a tax-deductible donation to the KDE e.V. foundation.

Categories: FLOSS Project Planets

Jonathan Dowland: hyperlinked PDF planner

Planet Debian - Fri, 2022-04-29 17:29

The Year page

A day page

I've been having reasonable success with time blocking, a technique I learned from Cal Newport's writings, in particular Deep Work. I'd been doing it on paper for a while, but I wanted to try and move to a digital solution.

There's a cottage industry of people making (and selling) various types of diary and planner as PDF files for use on tablets such as the Remarkable. Some of these use PDF hyperlinks to greatly improve navigating around. This one from Clou Media is particularly good, but I found that I wanted something slightly different from what I could find out there, so I decided to build my own.

I explored a couple of different approaches for how to do this. One was Latex, and here's one example of a latex-based planner, but I decided against as I spend too much time wrestling with it for my PhD work already.

Another approach might have been Pandoc, but as far as I could tell its PDF pipeline went via Latex, so I thought I might as well cut out the middleman.

Eventually I stumbled across tools to build PDFs from HTML, via "CSS Paged Media". This appealed, because I've done plenty of HTML generation. print-css.rocks is a fantastic resource to explore the print-specific CSS features. Weasyprint is a fantastic open source tool to convert appropriately-written HTML/CSS into PDF.

Finally I wanted to use a templating system to take shortcuts on writing HTML. I settled for embedded Ruby, which is something I haven't touched in over a decade. This was a relatively simple project and I found it surprisingly fun.

The results are available on GitHub: https://github.com/jmtd/planner. Right now, you get exactly what I have described. But my next plan is to add support for re-generating a planner, incorporating new information: pulling diary info from iCal, and any annotations made (such as with the Remarkable tablet) on top of the last generation and preserving them on the next.

Categories: FLOSS Project Planets

Steinar H. Gunderson: Should we stop teaching the normal distribution?

Planet Debian - Fri, 2022-04-29 14:30

I guess Betteridge's law of headlines gives you the answer, but bear with me. :-)

Like most engineers, I am a layperson in statistics; I had some in high school, then an intro course in university and then used it in a couple of random courses (like speech recognition). (I also took a multivariate statistics course on my own after I had graduated.) But pretty much every practical tool I ever learned was, eventually, centered around the normal distribution; we learned about Student's t-test in various scenarios, made confidence intervals, learned about the central limit theorem that showed its special place in statistics, how the binomial distribution converges to the normal distribution under reasonable circumstances (not the least due to the CLT), and so on.

But then I got out in the wild and started trying to make sense out of the troves of data coming my way (including some stemming from experiments I designed on my own). And it turns out… a lot of things really are not normal. I'd see distributions with heavy tails, with skew, or that were bimodal. And here's the thing—people, who had the same kind of non-statistics-specialized education as me, continued to treat these as Gaussian. And it still appears to work. You get the beautiful confidence intervals and low p-values that seem to make sense… it's just so odd that you get “p<0.05 significant“ tests way too often from random noise. You just assume that's how it is, without really realizing that you're doing junk statistics. And even if you do, you don't have the tools to do anything about it, because everything else is hidden away in obscure R libraries or somewhere on Math Stack Exchange.

So I ask: If we're really going to learn people one thing, is the normal distribution really the best tool? (Yes, sure, we learned about the Poission and Weibull and many others, but we never really did hypothesis testing on them, and we never really learned what to do when things didn't follow a tidy mathematical formula. Or even how to identify that.) It's beautiful and simple (“simple”) and mathematical and you only need a huge table and then you can almost do calculations by hand, but perhaps that's not really what we want? I understand we want to teach fundamental understanding and not just “use this computer tool”, but again, we're sending people out with a really limited tool set to make sense of the world.

I don't know what we should do instead—again, I am a layperson, and my understanding of this is limited. But it feels like we should be able to come up with fairly simple techniques that don't break down fatally if the data doesn't follow one given distribution, no matter how important. Bootstrap? Wilcoxon signed-rank test? I know, of course, that if the data really is normal, you will need a lot less data for the same-quality result (and some natural processes, like, I guess, radioactive decay, surely follow normal distributions), but perhaps we should leave the Gaussians and other parametric tools for the advanced courses? I don't know. But it's worth a thought. And I need to learn more statistics.

Categories: FLOSS Project Planets

Real Python: Real Python at PyCon US 2022

Planet Python - Fri, 2022-04-29 13:15

PyCon US is back as an in-person conference. PyCon US 2022 is happening in Salt Lake City April 29 to May 1, and Real Python is there as well. Come join us at our booth and at the open space on Saturday.

In this article, you’ll learn where you can find Real Python at PyCon in Salt Lake City, and get to know what some of our team members are excited about at the conference.

Meet Real Python at PyCon US 2022

The PyCon US conference has been an annual meeting place for the Python community since 2003. Because of the COVID pandemic, the conference went virtual in 2020 and 2021. At Real Python, we’re excited about being able to meet in person this year. Come say hello if you’re in Salt Lake City!

Visit the Real Python Booth

The exhibit hall is a lively place at any PyCon conference. Here, you can stroll around and chat with other attendees while exploring what sponsors and exhibitors have brought to the table. It’s a great place to hang out and make new friends!

Real Python has a booth at this year’s conference. We’re excited about having our own place to hang out and show our content to everyone. You can find us at booth 228, which is just opposite Microsoft and AWS. Look for our logo and friendly faces—we’ll be smiling with our eyes!

Stop by the booth to hear about all the content that we offer, or have a chat about your favorite packages, square roots, or the latest developments in Python.

Join Our Open Space

The open spaces are a unique staple of PyCon. These are self-organized one-hour meetup-like events that are happening throughout the conference. Check out the Open Space board to see if there’s anything that you’d like to join!

Read the full article at https://realpython.com/real-python-pycon-us-2022/ »

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Categories: FLOSS Project Planets

Holger Levsen: 20220429-Debian-Reunion-Hamburg-2022

Planet Debian - Fri, 2022-04-29 08:34

Debian Reunion Hamburg 2022 from May 23 to 30

This is just a quick reminder for the Debian Reunion Hamburg 2022 happening in a bit more than 3 weeks.

So far 43 people have registered and thus there's still some on site accomodation available. There's no real deadline for registration, however if you register after May 1st you might not get a t-shirt in your prefered size.

Also: if you attend to give a presentation but haven't replied to the CfP, please do so.

The wiki page linked above has all the details.

Categories: FLOSS Project Planets

Poppler finally has support for embedding fonts in PDF files!

Planet KDE - Fri, 2022-04-29 04:41

 Why would you want to embed fonts in PDF files are you probably asking yourself?

Short answer: It fixes issues when adding text to the PDF files.

Long answer:

Poppler has had the feature of being able to fill in forms, create annotations and more recently add Digital Signatures to existing PDF files.

This works relatively well if you limit yourself to entering 'basic' ASCII characters, but once you go to more 'complex' characters, things don't really work, from the outside it seems like it should be relatively simple to fix, but things related to PDF are never as simple as they may seem.

In PDF each bit of text is associated with a Font object. That Font generally only supports one kind of text encoding and at most 'only' 65535 characters (65535 may seem a lot, but once you start taking into account non latin-based languages, you quickly 'run out' of characters).

What Poppler used to do in the past was just save the text in the PDF file and say "This text is written in Helvetica font", without even really care to specify much what 'Helvetica font' meant,  and then let the PDF viewer (remember when we save the PDF file, it will not only be rendered by Poppler again, but potentially by Adobe Reader, Chrome, Firefox, etc.) try to figure out what to do with that information, which as said usually didn't go very well for the more 'complex' characters.

What we do now is for each character of new text that we add to the file is we make sure to embed a font for it. So if you're writing something like 'holaħŋ↓' we may end up adding a few fonts to the PDF file, and then instead of saying 'This is the text and it's in Helvetica, good luck', we will say something like 'This text is characters 4, 67, 83 and 98 of embedded Font X, characters 4 and 99 of embedded Font X2 and character 16574 of embedded Font X3'. This way when the file is opened by a PDF viewer it is 'very easy' for them to do the right thing and show what we wanted.

Enough of technical talk! Now some screenshots to show how this has been fixed for Text Annotations, Forms and Signatures :)

Writing "hello↓漢you" to a form





Signing a PDF file with my name being "Albeŋŧ As漢tals Ciđ"






Writing hola↓漢字 in a Text Annotation




Categories: FLOSS Project Planets

www @ Savannah: New free program needed

GNU Planet! - Fri, 2022-04-29 01:59

The world urgently needs a free program that can subtract background music from a field recording.

The purpose is to prevent censorship of people's video recordings of how cops treat the public.

Categories: FLOSS Project Planets

Russ Allbery: Review: Interesting Times

Planet Debian - Thu, 2022-04-28 22:50

Review: Interesting Times, by Terry Pratchett

Series: Discworld #17 Publisher: Harper Copyright: 1994 Printing: February 2014 ISBN: 0-06-227629-8 Format: Mass market Pages: 399

Interesting Times is the seventeenth Discworld novel and certainly not the place to start. At the least, you will probably want to read The Colour of Magic and The Light Fantastic before this book, since it's a sequel to those (although Rincewind has had some intervening adventures).

Lord Vetinari has received a message from the Counterweight Continent, the first in ten years, cryptically demanding the Great Wizzard be sent immediately.

The Agatean Empire is one of the most powerful states on the Disc. Thankfully for everyone else, it normally suits its rulers to believe that the lands outside their walls are inhabited only by ghosts. No one is inclined to try to change their minds or otherwise draw their attention. Accordingly, the Great Wizard must be sent, a task that Vetinari efficiently delegates to the Archchancellor. There is only the small matter of determining who the Great Wizzard is, and why it was spelled with two z's.

Discworld readers with a better memory than I will recall Rincewind's hat. Why the Counterweight Continent would demanding a wizard notorious for his near-total inability to perform magic is a puzzle for other people. Rincewind is promptly located by a magical computer, and nearly as promptly transported across the Disc, swapping him for an unnecessarily exciting object of roughly equivalent mass and hurling him into an unexpected rescue of Cohen the Barbarian. Rincewind predictably reacts by running away, although not fast or far enough to keep him from being entangled in a glorious popular uprising. Or, well, something that has aspirations of being glorious, and popular, and an uprising.

I hate to say this, because Pratchett is an ethically thoughtful writer to whom I am willing to give the benefit of many doubts, but this book was kind of racist.

The Agatean Empire is modeled after China, and the Rincewind books tend to be the broadest and most obvious parodies, so that was already a recipe for some trouble. Some of the social parody is not too objectionable, albeit not my thing. I find ethnic stereotypes and making fun of funny-sounding names in other languages (like a city named Hunghung) to be in poor taste, but Pratchett makes fun of everyone's names and cultures rather equally. (Also, I admit that some of the water buffalo jokes, despite the stereotypes, were pretty good.) If it had stopped there, it would have prompted some eye-rolling but not much comment.

Unfortunately, a significant portion of the plot depends on the idea that the population of the Agatean Empire has been so brainwashed into obedience that they have a hard time even imagining resistance, and even their revolutionaries are so polite that the best they can manage for slogans are things like "Timely Demise to All Enemies!" What they need are a bunch of outsiders, such as Rincewind or Cohen and his gang. More details would be spoilers, but there are several deliberate uses of Ankh-Morpork as a revolutionary inspiration and a great deal of narrative hand-wringing over how awful it is to so completely convince people they are slaves that you don't need chains.

There is a depressingly tedious tendency of western writers, even otherwise thoughtful and well-meaning ones like Pratchett, to adopt a simplistic ranking of political systems on a crude measure of freedom. That analysis immediately encounters the problem that lots of people who live within systems that rate poorly on this one-dimensional scale seem inadequately upset about circumstances that are "obviously" horrific oppression. This should raise questions about the validity of the assumptions, but those assumptions are so unquestionable that the writer instead decides the people who are insufficiently upset about their lack of freedom must be defective. The more racist writers attribute that defectiveness to racial characteristics. The less racist writers, like Pratchett, attribute that defectiveness to brainwashing and systemic evil, which is not quite as bad as overt racism but still rests on a foundation of smug cultural superiority.

Krister Stendahl, a bishop of the Church of Sweden, coined three famous rules for understanding other religions:

  1. When you are trying to understand another religion, you should ask the adherents of that religion and not its enemies.
  2. Don't compare your best to their worst.
  3. Leave room for "holy envy."

This is excellent advice that should also be applied to politics. Most systems exist for some reason. The differences from your preferred system are easy to see, particularly those that strike you as horrible. But often there are countervailing advantages that are less obvious, and those are more psychologically difficult to understand and objectively analyze. You might find they have something that you wish your system had, which causes discomfort if you're convinced you have the best political system in the world, or are making yourself feel better about the abuses of your local politics by assuring yourself that at least you're better than those people.

I was particularly irritated to see this sort of simplistic stereotyping in Discworld given that Ankh-Morpork, the setting of most of the Discworld novels, is an authoritarian dictatorship. Vetinari quite capably maintains his hold on power, and yet this is not taken as a sign that the city's inhabitants have been brainwashed into considering themselves slaves. Instead, he's shown as adept at maintaining the stability of a precarious system with a lot of competing forces and a high potential for destructive chaos. Vetinari is an awful person, but he may be better than anyone who would replace him. Hmm.

This sort of complexity is permitted in the "local" city, but as soon as we end up in an analog of China, the rulers are evil, the system lacks any justification, and the peasants only don't revolt because they've been trained to believe they can't. Gah.

I was muttering about this all the way through Interesting Times, which is a shame because, outside of the ham-handed political plot, it has some great Pratchett moments. Rincewind's approach to any and all danger is a running (sorry) gag that keeps working, and Cohen and his gang of absurdly competent decrepit barbarians are both funnier here than they have been in any previous book and the rare highly-positive portrayal of old people in fantasy adventures who are not wizards or crones. Pretty Butterfly is a great character who deserved to be in a better plot. And I loved the trouble that Rincewind had with the Agatean tonal language, which is an excuse for Pratchett to write dialog full of frustrated non-sequiturs when Rincewind mispronounces a word.

I do have to grumble about the Luggage, though. From a world-building perspective its subplot makes sense, but the Luggage was always the best character in the Rincewind stories, and the way it lost all of its specialness here was oddly sad and depressing. Pratchett also failed to convince me of the drastic retcon of The Colour of Magic and The Light Fantastic that he does here (and which I can't talk about in detail due to spoilers), in part because it's entangled in the orientalism of the plot.

I'm not sure Pratchett could write a bad book, and I still enjoyed reading Interesting Times, but I don't think he gave the politics his normal care, attention, and thoughtful humanism. I hope later books in this part of the Disc add more nuance, and are less confident and judgmental. I can't really recommend this one, even though it has some merits.

Also, just for the record, "may you live in interesting times" is not a Chinese curse. It's an English saying that likely was attributed to China to make it sound exotic, which is the sort of landmine that good-natured parody of other people's cultures needs to be wary of.

Followed in publication order by Maskerade, and in Rincewind's personal timeline by The Last Continent.

Rating: 6 out of 10

Categories: FLOSS Project Planets

Matt Glaman: drupal-check 1.4.0: enforcing PHPStan level 2

Planet Drupal - Thu, 2022-04-28 22:48

I have just tagged drupal-check 1.4.0. This is kind of a big deal. And it's either a great thing or is going to destroy my inbox with support requests. But, in my opinion, it is completely necessary.

Categories: FLOSS Project Planets

The Drop Times: Drupal 10, 11 and Dries

Planet Drupal - Thu, 2022-04-28 22:48
When Dries Buytaert took the stage to deliver the Driesnote for DrupalCon Portland he focused on two major points Drupal 10 and Drupal 11.
Categories: FLOSS Project Planets

The Drop Times: Dries: Drupal is for Ambitious Site Builders

Planet Drupal - Thu, 2022-04-28 22:12
Now that things with Drupal 10 seem to be moving in the right direction it’s time to start working and developing Drupal 11, said Dries at the DrupalCon Portland 2022.
Categories: FLOSS Project Planets

Web Wash: How to Host and Deploy Drupal Sites using Cloudways

Planet Drupal - Thu, 2022-04-28 21:37

Cloudways is a managed cloud hosting provider which allows you to host and deploy Drupal sites on popular cloud platforms. They don’t host your site, instead you choose a cloud provider, i.e., DigitalOcean, Linode, AWS and so on.

The benefit of using Cloudways is that you can start on a cheap server and upgrade as needed. You get the same features if you spend $20 or $100 per month. As your site gets more traffic you can scale up to a faster server.

The server provisioning and application management is all done via the Cloudways platform. For example, if you use DigitalOcean, you won’t have to create a separate account to manage the server. You can do everything via Cloudways platform.

The servers come configured with PHP, Apache, MariaDB, Varnish, Redis (uninstalled by default) and so on.

Once provisioned you’ll be able to host an unlimited number of Drupal sites on a single server. You’ll need to make sure your server has enough resources, i.e., RAM and CPU.

Other hosting platforms charge per site and traffic. Cloudways only charger per server and you can install as many websites as the server and bandwidth can handle.

In this tutorial, you’ll learn how to install and deploy a Drupal site on Cloudways.

We’ll cover how to install and run Drupal in two ways:

  • How to manually install Drupal via SSH
  • How to Deploy Drupal using GitHub

Cloudways doesn’t have a one-click install for Drupal. You will need to be comfortable with Composer and using SSH to get into a server. If you want to deploy your Drupal site via Git then you’ll need to be comfortable using GitHub.

Categories: FLOSS Project Planets