Shared posts

08 Jul 15:07

Episode 165 - Max Blumenthal Responds to Ro Khanna

Tom Roche

Max Blumenthal is EXCELLENT as usual, and better-than-expected chemistry with Brie (who is, deep down, a Nice White Girl)

Subscribe to Bad Faith on Patreon to instantly unlock our full premium episode library: http://patreon.com/badfaithpodcast  

 

We've edited down last Thursday's Callin/YouTube live stream with Gray Zone journalist Max Blumenthal for your listening pleasure. We felt this was mandatory listening. We hope you agree.

Subscribe to Bad Faith on YouTube for video of this episode. Find Bad Faith on Twitter (@badfaithpod) and Instagram (@badfaithpod).

Produced by Armand Aviram.

Theme by Nick Thorburn (@nickfromislands).

18 Apr 14:12

Behind the News: The Population of Our Prisons w/ Wanda Bertram

Tom Roche

Bertram/1st segment is good, Chappel/2nd segment is skippable

Doug speaks with Wanda Bertram of the Prison Policy Initiative about the demographics of the million people in state prisons and the fight around cash bail in New York. Plus, Doug talks to historian James Chappel about his recent article "Inside the Postliberal Mind" which reviews a new book by reactionary Catholic law professor Adrian Vermeule.


Behind the News, hosted by Doug Henwood, covers the worlds of economics and politics and their complex interactions, from the local to the global. Find the archive here: https://www.leftbusinessobserver.com/Radio.html



Our GDPR privacy policy was updated on August 8, 2022. Visit acast.com/privacy for more information.

18 Apr 02:39

Irreal: Emacs and Calibre

by jcs
Tom Roche

TODO: investigate [Calibre](https://calibre-ebook.com/) and [calibredb.el](https://github.com/chenyanming/calibredb.el) for reading/annotating ebooks (and PDFs?)

Garjola Dindi has a very interesting post on using Calibre from within Emacs. Dindi already read his books from within Emacs using nov.el for Epubs and pdf-tools for PDFs but because he manages his library with Calibre, he had to leave Emacs and open Calibre to handle administrative chores on his library.

Recently, he (re)discovered calibredb.el and realized that he could use it to handle those administrative tasks without leaving Emacs. Calibredb.el wants to use ebook-reader and evince to open books so Dindi had a make a simple adjustment for that. Otherwise, everything worked out of the box.

I do almost all of my reading on an iPad so this doesn’t come up for me even though I have used Calibre on my laptop in the past. I sometimes read technical books on my laptop but I usually just fire up pdf-tools directly to do it. Dindi reminded me, though, that there’s a lot of benefit to reading technical material on a laptop: it’s easy to take notes with Org-mode and to leave bookmarks or other links into the book you’re reading.

The downside is that I haven’t been very diligent about organizing my books so I always have to go searching for them. That’s exactly the problem that Calibre solves, of course, so Dindi’s post has inspired me to consider reinstalling Calibre and getting my library in shape. At least for technical books, that makes a lot of sense.

17 Apr 22:06

Radio War Nerd EP 325 — Modern Urban Warfare: Marawi, Raqqa, Mosul, Mariupol

by mail@yashalevine.com (Gary Brecher)
Tom Roche

EXCELLENT survey (recorded 7 Apr 2022), including

1. Russia-Ukraine war status, esp leadup from 2014
2. overview of themes and topics in modern urban warfare, applied to the following cases (with mentions of a few more):
- Marawi 2017
- Raqqa 2016-2017
- information warfare and media-access control
- Jenin 2002
- Mosul 2016-2017
- Fallujah 2004
- Mariupol 2022

17 Apr 14:33

Talk Python to Me: #361: Pangeo Data Ecosystem

Python's place in climate research is an important one. In this episode, you'll meet Joe Hamman and Ryan Abernathey, two researchers using powerful cloud computing systems and Python to understand how the world around us is changing. They are both involved in the Pangeo project which brings a great set of tools for scaling complex compute with Python.<br/> <br/> <strong>Links from the show</strong><br/> <br/> <div><b>Ryan Abernathey</b>: <a href="https://twitter.com/rabernat" target="_blank" rel="noopener">@rabernat</a><br/> <b>Joe Hamman</b>: <a href="https://twitter.com/HammanHydro" target="_blank" rel="noopener">@HammanHydro</a><br/> <b>Pangeo</b>: <a href="https://pangeo.io/" target="_blank" rel="noopener">pangeo.io</a><br/> <b>xarray</b>: <a href="https://xarray.dev/" target="_blank" rel="noopener">xarray.dev</a><br/> <b>Pangeo Forge</b>: <a href="https://pangeo-forge.org/" target="_blank" rel="noopener">pangeo-forge.org</a><br/> <b>fsspec</b>: <a href="https://filesystem-spec.readthedocs.io/en/latest/features.html" target="_blank" rel="noopener">filesystem-spec.readthedocs.io</a><br/> <b>Step-by-Step Guide to Building a Big Data Portal</b>: <a href="https://medium.com/pangeo/step-by-step-guide-to-building-a-big-data-portal-e262af1c2977" target="_blank" rel="noopener">medium.com</a><br/> <b>Coiled</b>: <a href="https://coiled.io/" target="_blank" rel="noopener">coiled.io</a><br/> <b>Pangeo Gallery</b>: <a href="http://gallery.pangeo.io/" target="_blank" rel="noopener">gallery.pangeo.io</a><br/> <b>Pangeo Quickstart</b>: <a href="https://pangeo.io/quickstart.html#quickstart" target="_blank" rel="noopener">pangeo.io</a><br/> <b>JupyterLite</b>: <a href="https://jupyterlite.readthedocs.io/en/latest/" target="_blank" rel="noopener">jupyterlite.readthedocs.io</a><br/> <b>Jupyter</b>: <a href="https://jupyter.org/" target="_blank" rel="noopener">jupyter.org</a><br/> <b>Pangeo Packages</b>: <a href="https://pangeo.io/packages.html#packages" target="_blank" rel="noopener">pangeo.io</a><br/> <b>Pangeo Discourse</b>: <a href="https://discourse.pangeo.io/" target="_blank" rel="noopener">discourse.pangeo.io</a><br/> <b>Watch this episode on YouTube</b>: <a href="https://www.youtube.com/watch?v=T3jUatZ1KTo" target="_blank" rel="noopener">youtube.com</a><br/> <b>Episode transcripts</b>: <a href="https://talkpython.fm/episodes/transcript/361/pangeo-data-ecosystem" target="_blank" rel="noopener">talkpython.fm</a><br/> <br/> <b>--- Stay in touch with us ---</b><br/> <b>Subscribe on YouTube</b>: <a href="https://talkpython.fm/youtube" target="_blank" rel="noopener">youtube.com</a><br/> <b>Follow Talk Python on Twitter</b>: <a href="https://twitter.com/talkpython" target="_blank" rel="noopener">@talkpython</a><br/> <b>Follow Michael on Twitter</b>: <a href="https://twitter.com/mkennedy" target="_blank" rel="noopener">@mkennedy</a><br/></div><br/> <strong>Sponsors</strong><br/> <a href='https://talkpython.fm/signalwire'>SignalWire</a><br> <a href='https://talkpython.fm/sentry'>Sentry Error Monitoring, Code TALKPYTHON</a><br> <a href='https://talkpython.fm/training'>Talk Python Training</a>
17 Apr 02:21

Jacobin Show: The Downfall of Defund w/ Cedric Johnson

Tom Roche

unfortunately rather lame: the Zamora segment is quite short (and doesn't actually 'how' this happened, just states that it did), the Pan segment doesn't add much (yes, both the CorpDems and CorpReps want to spend more on the US military--did someone not know this?), and the Cedric Johnson segment is disappointing in ways that would take longer to discuss than I presently have time for

Jen Pan takes a look at the politics of the pentagon budget and Daniel Zamora discusses how American-style identity politics somehow found a way of suffusing the ideological debates around the French election. Then, Jen sits down with Cedric Johnson to discuss why the defund movement failed, and why the Left should prioritize a politics of increased social spending to eliminate the very basis of what Johnson terms "stress policing" of working-class communities.


Subscribe to Jacobin for just $10: https://jacobinmag.com/subscribe/?code=JACOBINYT


Music provided by Zonkey: https://linktr.ee/zonkey


The Jacobin Show offers socialist perspectives on class and capitalism in the twenty-first century, the failures of liberalism, and the prospects of rebuilding a left labor movement in the US. This is the podcast version of the show from April 13, 2022.



Our GDPR privacy policy was updated on August 8, 2022. Visit acast.com/privacy for more information.

17 Apr 02:18

Michael and Us: The Tao of Jack Ryan

Tom Roche

VERY EXCELLENT: and funny as usual

Luke and Will delve into the world of right-wing legend Tom Clancy and his signature character Jack Ryan with the blockbuster film adaptation of THE HUNT FOR RED OCTOBER (1990). PLUS: Winston Churchill predicts the future!


Michael and Us is a podcast about political cinema and our crumbling world hosted by Will Sloan and Luke Savage.



Our GDPR privacy policy was updated on August 8, 2022. Visit acast.com/privacy for more information.

17 Apr 02:17

A World to Win: Who Benefited from Britain's Empire? w/ Kojo Koram

Tom Roche

unfortunately rather lame: instead of discussing the advertised topic ("Who Benefited from Britain's Empire?"), they discuss the *need* to discuss the advertised topic, who doesn't want to discuss the advertised topic, etc

Grace talks to Kojo Koram, who teaches in the School of Law at Birkbeck College and is the author of Uncommon Wealth: Britain and the Aftermath of Empire, about why the government is trying to change the curriculum to include a more 'balanced' perspective on Britain's empire. We ask who actually benefited from the days of formal empire, how imperialism continues to this day, and why the right are so keen to keep the culture wars alive. 


A World to Win is a podcast from Grace Blakeley and Tribune bringing you a weekly dose of socialist news, theory and action with guests from around the world. Thanks to producer Sarah Hurd for filling in this week and to the Lipman-Miliband Trust for making this episode possible.



Our GDPR privacy policy was updated on August 8, 2022. Visit acast.com/privacy for more information.

16 Apr 16:29

Ep 255 It’s a Financial War feat Tom Luongo

Tom Roche

Luongo is as usual a mix of interesting information with nonsense ideology (e.g., Austrian economics, anarcho-libertarianism) and straight-up batshit crazyness (e.g., gold, UK)

Guest: Tom Luongo. We continue and update our conversation from Episode 229 Central Bank Wars about the financial war underlying the conflicts and crises around the world right now. We discuss the global financial infrastructure, LIBOR and it’s replacement, SOFR, the Federal Reserve, the great power wars, the hot war in Ukraine, and more. In a long bonus episode we talk about whether Russia and China are part of the Great Reset and “in on it”, so to speak, taking into consideration some arguments from both sides of that question. And more.

Tom Luongo is a former research chemist, amateur dairy goat farmer, anarcho-libertarian, and obstreperous Austrian economist. His specialty is connecting geopolitics and finance/investment and he publishes the Gold Goats, and Guns newsletter and podcast and whose work can be found on sites like Zerohedge, Lewrockwell.com, Bitcoin Magazine and Newsmax Media.

FOLLOW Tom at @TFL1728. Find his work at the Gold, Goats ‘n Guns website, on Youtube, Odysee, Patreon and other platforms.

Around the Empire aroundtheempire.com is listener supported, independent media.

SUBSCRIBE/FOLLOW on Rokfin rokfin.com/aroundtheempire, Patreon patreon.com/aroundtheempire, Paypal paypal.me/aroundtheempirepod, YouTube youtube.com/aroundtheempire, Spotify, iTunes, iHeart, Google Podcasts

FOLLOW @aroundtheempire and @joanneleon.  Join us on TELEGRAM https://t.me/AroundtheEmpire

Find everything on http://aroundtheempire.com  and linktr.ee/aroundtheempire

Recorded on March 31, 2022. Music by Fluorescent Grey.

Reference Links:

  1. ATE Ep 229 Central Bank Wars feat Tom Luongo
  2. GGG Podcast Episode #75 – Davos and the Week That Changed Everything Part 1
  3. GGG Podcast Episode #76 – Davos, Ukraine and the Week That Changed the World Part II
  4. SOFR v. LIBOR: Have We Been Missing Something About the Fed’s Upcoming “Policy Error?”, Tom Luongo
  5. The Putin Interviews, Oliver Stone
  6. Tom Luongo: Can Russia Kill the Great Reset? Palisades Gold Radio
15 Apr 18:15

Breaking Points LIVE FROM NEW YORK featuring Kyle Kulinski & Marshall Kosloff!

Tom Roche

unlike BP *shows*, this live meetup is just them doing takes: no new or even interesting data.

Krystal and Saagar go live from New York with Kyle Kulinski and Marshall Kosloff to talk about Elon's Twitter offer, housing market, the lost healthcare debate, energy policy, geopolitical realignment, and labor activism!


To become a Breaking Points Premium Member and watch/listen to the show uncut and 1 hour early visit: https://breakingpoints.supercast.com/


To listen to Breaking Points as a podcast, check them out on Apple and Spotify


Apple: https://podcasts.apple.com/us/podcast/breaking-points-with-krystal-and-saagar/id1570045623 


Spotify: https://open.spotify.com/show/4Kbsy61zJSzPxNZZ3PKbXl 


Merch: https://breaking-points.myshopify.com/

Learn more about your ad choices. Visit megaphone.fm/adchoices

15 Apr 02:03

Emacs TIL: Gusto Engineering Blog Article: My Plaintext Workflow

by Junji Zhi
Tom Roche

this is the subject of Irreal's post title='Second Brain in Plain Text': see article @ [medium](https://medium.com/gusto-engineering/a-developers-second-brain-on-plaintext-ac96492eff2e) (archived [here](https://archive.ph/p7wyM))

Today I published an article in Gusto Engineering Blog. It is about how I manage my working memory with a single plaintext file: wm.txt. And later the workflow evolved into a wm.org file and leverage all the power of org-mode!

Feel free to check out the article!

13 Apr 13:11

Borisov's Big Pile of Money feat. Madlen Nikolova

by The Späti Boys
Tom Roche

quite funny, if unfortunately for Bulgaria (and their neighbors--suck it up, Macedonia :-)

We catch up on the year of elections in Bulgaria and the "post-political" parties that have emerged. We have Madlen Nikolova on to talk about of this and more.

OUR GUEST:
https://twitter.com/NikolovaMadlen
https://newleftreview.org/sidecar/posts/115

HOW TO SUPPORT US:
https://www.patreon.com/cornerspaeti

HOW TO REACH US:
Corner Späti https://twitter.com/cornerspaeti
Julia https://twitter.com/KMarxiana
Rob https://twitter.com/leninkraft
Nick https://twitter.com/sternburgpapi
Ciarán https://twitter.com/CiaranDold

Support Corner Späti

13 Apr 03:59

618 - Z Whispers (4/11/22)

Tom Roche

excellent as usual, esp intro bant (usually my least favorite part of CTH) on seed oils, credit cards, microplastics, etc

We’re getting absolutely zooted on microplastics for today’s ep, then looking at Bill Clinton’s “if I didn’t do it” article in the Atlantic on his true intentions behind NATO expansion in the 90’s, and another piece on what the Democrats can do to recapture Gen Z for the midterms.

13 Apr 03:32

4/12/22: NATO Expansion, Elon vs Twitter, Inflation Data, Covid, Gen Z Misery, Kushner Corruption, Dem Warning, & Yemen Disaster!

Tom Roche

mostly good except for COVID-19 rant (I agree about Philadelphia but, ... Saagar cares waaay too much about this)

Krystal and Saagar bring the news about NATO expansion, alleged attacks in Ukraine, Elon Musk's conflict with Twitter, historic inflation numbers for March, a reintroduction of covid guidelines, Gen Z's misery, Jared Kushner corruption, French elections' warning for Dems, and all things on the Saudi assault on Yemen!


To become a Breaking Points Premium Member and watch/listen to the show uncut and 1 hour early visit: https://breakingpoints.supercast.com/


To listen to Breaking Points as a podcast, check them out on Apple and Spotify


Apple: https://podcasts.apple.com/us/podcast/breaking-points-with-krystal-and-saagar/id1570045623 


Spotify: https://open.spotify.com/show/4Kbsy61zJSzPxNZZ3PKbXl 


Merch: https://breaking-points.myshopify.com/


Dr. Annelle Sheline: https://www.1833stopwar.com/ 

https://responsiblestatecraft.org/author/asheline/ 

Learn more about your ad choices. Visit megaphone.fm/adchoices

12 Apr 23:36

You Heard It Here First

Tom Roche

stereotypical BBC quiz show: entertaining without really being about much of anything (in this case, making guesses based on audio cues). More energetic than usual, probably worth your 30 min

Chris McCausland asks a panel of comedians to live in an audio only world, deciphering brainteaser sound cues for points and pride whilst trying not to muck about too much along the way. In this pilot episode, contestants try and figure out exactly what it is that they’re watching on the tele and which celebrity is talking to them with a mouthful of cake. The competing comedians are Mark Maier, Amy Gledhill, Marlon Davis and Eleanor Tiernan. Producer: Richard Morris Production co-ordinator: Beverly Tagg Sound editor: David Thomas Theme music 'Colour me Groovy' by The Rich Morton Sound Recorded at the Backyard Comedy Club, Bethnal Green
12 Apr 16:12

Radio War Nerd EP 324 — Chechen Wars, Pt. 3: The First Chechen War

by mail@yashalevine.com (Gary Brecher)
Tom Roche

VERY EXCELLENT--does the 1991-1994 leadup to the 1st Chechen War, as well as the 1CW 1994-1996

Co-hosts Gary Brecher & Mark Ames
12 Apr 01:51

Irreal: A Research Work Flow in Emacs and Org-mode

by jcs
Tom Roche

TODO: investigate this [Org+Zotero+{few more packages} toolset/workflow](https://swalla24.people.uic.edu/org-research/) (archived [here](https://archive.ph/jvqNk)) esp re

* notetaking
* separately-integrating classwork and research

Sam Wallace has an interesting post on how he takes research notes. He’s a Mathematics PhD student so he reads lots of papers that he has to take notes on as well as class notes and his own research projects. The post is an explanation of how he leverages Emacs and Org-mode to organize his notes and workflow.

He mainly uses Org-mode, reinforced with CDLaTeX, to take his notes. His approach was inspired by Gilles Castel’s post, My Mathematics PhD research workflow, which is similar but uses Vim instead of Emacs to tie everything together. I’ve written about Castel and his astounding Mathematics note taking procedures before.

The interesting thing about Wallace’s workflow is that although he uses some external tools and packages, such as Zotero, Org-noter, and ivy-bibtex, it mainly rests on Org-mode. Org is an excellent way of organizing notes and research no matter what field you’re in but Org’s built-in LaTeX support augmented by CDLaTeX for efficient entry makes it particularly well suited for Mathematics.

If you’re a PhD student or researcher, you really should take a look at Wallace’s and Castel’s posts. They’ve both got a lot of good ideas on how to get organized.

11 Apr 14:50

Australia's economic choices: China dependence

Tom Roche

part 2 of Satyajit Das on Australia in the global economy

In the second part of this series drawing on themes discussed in Satyajit Das' new book Fortune's Fool: Australia's Choices we discuss the challenges facing China’s economy, and where this leaves Australia, given our current economic dependence on China.
11 Apr 14:48

Australia's economic choices: Post-pandemic trends

Tom Roche

while I don't usually agree with Satyajit Das' politics, his economic takes are usually excellent

Over the next few months, we'll be discussing some of the economic choices and challenges facing Australia, drawing on themes discussed in Satyajit Das' new book Fortune's Fool: Australia's Choices. This month, we take a look at the social and economic faultlines that have been exposed by the Covid-19 pandemic, and some of the trends that we're seeing in the domestic economy as a result of several challenging headwinds.
10 Apr 18:33

Trade unions: everything you wanted to know

Tom Roche

VERY EXCELLENT--necessarily shallow (200 years in ~50 min) but well done. TODO: campaign for similar piece on US labor and unionism.

Mark Crail tackles popular internet search queries and listener questions about the history of Britain’s trade union movement and its attempts to secure better conditions for the country’s workers. He talks to Jon Bauckham about the 19th-century origins of the unions, their connection with the Labour Party, and their role in strikes through the centuries.

See acast.com/privacy for privacy and opt-out information.

09 Apr 18:22

Mini Show #30: BlackRock, Nuclear Power, Corporate Profits, Media Influence, Twitter, Kamala, Corporate Mergers, & More!

Tom Roche

This "Saturday mini-show" breaking points (from S 9 Apr 2022) is full-length (93 min, minus the intro/ad), and ... mostly OK but also mostly-skippable ... *except* for the EXCELLENT closer (~20 min, 1:13:29 to end of audio) by Matt Stoller on US corporate mergers esp after the 1982 Stigler-Posner suppression of Clayton Act enforcement. Call for FTC action (needed <= H 21 Apr 2022) @

- comment on FTC Merger Enforcement directly [here](https://www.regulations.gov/docket/FTC-2022-0003/document) ...
- ... or indirectly via [AELP](https://secure.everyaction.com/FNcO8Kqp_kGsVUT1sYEC1g2) (which is Stoller's thinktank/policyshop) ...
- ... or via the [FTC's topic page](https://www.ftc.gov/news-events/news/press-releases/2022/01/federal-trade-commission-justice-department-seek-strengthen-enforcement-against-illegal-mergers) ([archived](https://archive.ph/wOnY1))

Krystal and Saagar discuss BlackRock's climate profiteering, MSNBC shakeup, corporate profits, nuclear energy, Tucker's influence, NYT audience, Kamala's incoherent answers, Elon's Twitter play, and corporate mergers with Matt Stoller!


To become a Breaking Points Premium Member and watch/listen to the show uncut and 1 hour early visit: https://breakingpoints.supercast.com/


To listen to Breaking Points as a podcast, check them out on Apple and Spotify


Apple: https://podcasts.apple.com/us/podcast/breaking-points-with-krystal-and-saagar/id1570045623 


Spotify: https://open.spotify.com/show/4Kbsy61zJSzPxNZZ3PKbXl 


Merch: https://breaking-points.myshopify.com/


The Lever: https://www.levernews.com/


Matt Stoller Links: https://secure.everyaction.com/FNcO8Kqp_kGsVUT1sYEC1g2

https://www.ftc.gov/news-events/news/press-releases/2022/01/federal-trade-commission-justice-department-seek-strengthen-enforcement-against-illegal-mergers

https://mattstoller.substack.com/


Meredith Angwin: https://meredithangwin.com/

Learn more about your ad choices. Visit megaphone.fm/adchoices

09 Apr 16:07

The Leak

Tom Roche

EXCELLENT funny as usual, esp dissecting the weird, wacky, and thoroughly-deplorable politics beneath The West Wing

In an effort to shine a light on stories the mainstream media avoids, Josh and Dave discuss the recent Academy Awards, and a thing that happened at them. Plus - did not see this coming - The West Wing comes down on the wrong side of Intelligent Design.

Also - Is this the final Psaki Bomb?????

09 Apr 16:05

Python GUIs: Packaging PyQt5 applications into a Linux package with PyInstaller (updated for 2022)

Tom Roche

long/detailed article includes not only building PyQt5 app to executable, but also using [FPM](https://www.digitalocean.com/community/tutorials/how-to-use-fpm-to-easily-create-packages-in-multiple-formats) to build a .deb (Note FPM can also build .rpm)

In the previous tutorials, we've looked at packaging your PyQt5 applications for Windows and macOS -- turning them into EXE Installers and macOS bundles respectively. But to make your application truly cross-platform you should also provide installers for Linux. In this tutorial we'll look at how to do just that, first using PyInstaller to bundle our application into a executable app and then using a tool called fpm to convert that into a Linux package.

This tutorial is broken down into a series of steps, using PyInstaller to build first simple, and then more complex PyQt5 applications into Linux executables. 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 an Ubuntu .deb package, the usual method for distributing application on that systems. Thanks to the magic of fpm the instructions will also work for other Linux distributions, such as Redhat .rpm or Arch .pacman.

You always need to compile your app on your target system. So, if you want to create an Ubuntu package do this on Ubuntu.

Example Ubuntu Package Example Ubuntu Package

If you're impatient, you can download the Example Ubuntu Package first.

Requirements

PyInstaller works out of the box with PyQt5 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. This tutorial assumes you have a working installation of Python with pip package management working.

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 PyQt5 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 PyQt5 you would use —

python
pip3 install PyQt5 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 PyQt5 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 Ubuntu).

Simple skeleton app in PyQt5 Simple skeleton app in PyQt5

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 (shell) and navigate to the folder containing your project. You can now run the following command to run the PyInstaller build.

python
pyinstaller app.py

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
$ pyinstaller app.py
85 INFO: PyInstaller: 4.10
85 INFO: Python: 3.9.7
88 INFO: Platform: Linux-5.13.0-39-generic-x86_64-with-glibc2.34
89 INFO: wrote /home/martin/pyinstaller/linux2/no-datas/pyqt5/app.spec
91 INFO: UPX is not available.
91 INFO: Extending PYTHONPATH with paths
['/home/martin/pyinstaller/linux2/no-datas/pyqt5']
236 INFO: checking Analysis
240 INFO: Building because inputs changed
240 INFO: Initializing module dependency graph...
243 INFO: Caching module graph hooks...
255 INFO: Analyzing base_library.zip ...
2008 INFO: Processing pre-find module path hook distutils from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks/pre_find_module_path/hook-distutils.py'.
2013 INFO: distutils: retargeting to non-venv dir '/usr/lib/python3.9'
4231 INFO: Caching module dependency graph...
4348 INFO: running Analysis Analysis-00.toc
4379 INFO: Analyzing /home/martin/pyinstaller/linux2/no-datas/pyqt5/app.py
4403 INFO: Processing module hooks...
4403 INFO: Loading module hook 'hook-PyQt5.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4559 WARNING: Hidden import "sip" not found!
4559 INFO: Loading module hook 'hook-xml.etree.cElementTree.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4560 INFO: Loading module hook 'hook-heapq.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4562 INFO: Loading module hook 'hook-distutils.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4568 INFO: Loading module hook 'hook-xml.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4627 INFO: Loading module hook 'hook-PyQt5.QtWidgets.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4709 INFO: Loading module hook 'hook-difflib.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4711 INFO: Loading module hook 'hook-multiprocessing.util.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4712 INFO: Loading module hook 'hook-sysconfig.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4713 INFO: Loading module hook 'hook-encodings.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4759 INFO: Loading module hook 'hook-PyQt5.QtGui.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4807 INFO: Loading module hook 'hook-lib2to3.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4820 INFO: Loading module hook 'hook-pickle.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4827 INFO: Loading module hook 'hook-PyQt5.QtCore.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4853 INFO: Loading module hook 'hook-distutils.util.py' from '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks'...
4862 INFO: Looking for ctypes DLLs
4897 INFO: Analyzing run-time hooks ...
4900 INFO: Including run-time hook '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_subprocess.py'
4903 INFO: Including run-time hook '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_pkgutil.py'
4905 INFO: Including run-time hook '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_multiprocessing.py'
4910 INFO: Including run-time hook '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_inspect.py'
4912 INFO: Including run-time hook '/home/martin/.local/lib/python3.9/site-packages/PyInstaller/hooks/rthooks/pyi_rth_pyqt5.py'
4916 INFO: Looking for dynamic libraries
6561 INFO: Looking for eggs
6561 INFO: Python library not in binary dependencies. Doing additional searching...
6596 INFO: Using Python library /lib/x86_64-linux-gnu/libpython3.9.so.1.0
6604 INFO: Warnings written to /home/martin/pyinstaller/linux2/no-datas/pyqt5/build/app/warn-app.txt
6625 INFO: Graph cross-reference written to /home/martin/pyinstaller/linux2/no-datas/pyqt5/build/app/xref-app.html
6643 INFO: checking PYZ
6645 INFO: Building because name changed
6645 INFO: Building PYZ (ZlibArchive) /home/martin/pyinstaller/linux2/no-datas/pyqt5/build/app/PYZ-00.pyz
6923 INFO: Building PYZ (ZlibArchive) /home/martin/pyinstaller/linux2/no-datas/pyqt5/build/app/PYZ-00.pyz completed successfully.
6926 INFO: checking PKG
6926 INFO: Building because name changed
6927 INFO: Building PKG (CArchive) app.pkg
6959 INFO: Building PKG (CArchive) app.pkg completed successfully.
6962 INFO: Bootloader /home/martin/.local/lib/python3.9/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run
6963 INFO: checking EXE
6963 INFO: Building because name changed
6964 INFO: Building EXE from EXE-00.toc
6969 INFO: Copying bootloader EXE to /home/martin/pyinstaller/linux2/no-datas/pyqt5/build/app/app
6970 INFO: Appending PKG archive to custom ELF section in EXE
6979 INFO: Building EXE from EXE-00.toc completed successfully.
6981 INFO: checking COLLECT
6982 INFO: Building COLLECT COLLECT-00.toc
8674 INFO: Building COLLECT COLLECT-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 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 localpycos
&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 lib-dynload
    &boxv   &boxvr&boxh&boxh PyQt5
    &boxv   ...
    &boxv   &boxvr&boxh&boxh app
    &boxv   &boxur&boxh&boxh libQt5Core.so.5
    &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 PyQt5) and binary .so files.

Everything necessary to run your application will be in this folder, meaning you can take this folder and "distribute" it to someone else to run your app.

You can try running your app yourself now, by running the executable file, named app from the dist folder. After a short delay you'll see the familiar window of your application pop up as shown below.

Simple app, running after being packaged 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. 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=True,
          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')

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.

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 things we can do to tweak our build.

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 your executable file (and dist folder) by editing the .spec file to add a name= under the EXE and COLLECT blocks. On Linux you will want to use a name with no spaces (use hyphens instead).

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')

The name under EXE is the name of the executable file, the name under COLLECT is the name of the output folder. Usually you would want these to be the same.

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" app.py
# or
pyinstaller --name "hello-world" app.py

The resulting executable file will be given the name hello-world and the unpacked build placed in the folder dist\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.

If you've created a new .spec delete the old one to avoid getting confused!

Application with custom name Application with custom name "hello-world"

Application icon

One simple improvement we can make is to change the application icon which is shown while the application is running. We can set this icon in the code directly. To show an icon on our window we need to modify our simple application a little bit, to add a call to .setWindowIcon().

python
from PyQt5 import QtWidgets, QtGui
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)
    app.setWindowIcon(QtGui.QIcon('penguin.svg'))
    w = MainWindow()
    app.exec()

Here we've added the .setWindowIcon call to the app instance. This defines a default icon to be used for all windows of our application. You can override this on a per-window basis if you like, by calling .setWindowIcon on the window itself.

If you run the above application you should now see the icon appears on the dock.

Window showing the custom penguin icon Window showing the custom penguin icon

You can use a PNG file instead of SVG, but if using PNG make sure that the icon is large enough not to appear blurry due to scaling.

Even if you don't see the icon, 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 icon missing Window with icon 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 you don't know what the current working directory will be when it is run -- if it's wrong 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 PyQt5 import QtWidgets, QtGui
import sys, os

basedir = os.path.dirname(__file__)


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)
    app.setWindowIcon(QtGui.QIcon(os.path.join(basedir, 'penguin.svg')))
    w = MainWindow()
    app.exec_()

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

With this added to your script, running it should now show the icon on your window and taskbar. The final step is to ensure that this icon is correctly packaged with your application and continues to be shown when run from the dist folder.

Try it, it wont.

The issue is that our application now has a dependency on a external data file (the icon file) that's not part of our source. For our application to work, we now need to distribute this data file along with it. PyInstaller can do this for us, but we need to tell it what we want to include, and where to put it in the output.

In the next section we'll look at the options available to you for managing data files associated with your app.

Data files and Resources

So far we successfully built a simple app which had no external dependencies. However, once we needed to load an external file (in this case an icon) we hit upon a problem. The file wasn't copied into our dist folder and so could not be loaded.

In this section we'll look at the options we have to be able to bundle external resources, such as icons or Qt Designer .ui files, with our applications.

Bundling data files with PyInstaller

The simplest way to get these data files into the dist folder is to just tell PyInstaller to copy them over. PyInstaller accepts a list of individual file paths to copy over, 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, --add-data

bash
pyinstaller --add-data "penguin.svg:." --name "hello-world" app.py

You can provide `--add-data` multiple times. Note that the path separator is platform-specific, on Linux or Mac use `:` while on Windows use `;`

Or via the datas list in the Analysis section of the spec file, shown below.

python
a = Analysis(['app.py'],
             pathex=[],
             binaries=[],
             datas=[('penguin.svg', '.')],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)

And then execute the .spec file with

bash
pyinstaller hello-world.spec

In both cases we are telling PyInstaller to copy the specified file hand.ico to the location . which means the output folder dist. We could specify other locations here if we wanted. On the command line the source and destination are separated by the path separator :, whereas in the .spec file, the values are provided as a 2-tuple of strings.

If you run the build, you should see your .svg file now in the output folder dist ready to be distributed with your application.

The icon file copied to the dist folder The icon file copied to the dist folder

If you run your app from dist you should now see the icon on the window, and on the taskbar as expected.

The penguin icon showing on the dock The penguin icon showing on the dock

The file must be loaded in Qt using a relative path, and be in the same relative location to the EXE as it was to the .py file for this to work.

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. For example, lets extend our app to add some additional icons, and put them under a folder.

python
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QVBoxLayout, QPushButton, QWidget
from PyQt5.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)

        button = QPushButton("Push")
        button.setIcon(QIcon(os.path.join(basedir, "icons", "lightning.svg")))
        button.pressed.connect(self.close)
        layout.addWidget(button)

        container = QWidget()
        container.setLayout(layout)

        self.setCentralWidget(container)

        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setWindowIcon(QIcon(os.path.join(basedir, "icons", "penguin.svg")))
    w = MainWindow()
    app.exec_()

The icons (both SVG files) are stored under a subfolder named 'icons'.

bash
.
&boxvr&boxh&boxh app.py
&boxur&boxh&boxh icons
    &boxur&boxh&boxh lightning.svg
    &boxur&boxh&boxh penguin.svg

If you run this you'll see the following window, with an icon on the button and an icon in the dock.

Two icons Window with two icons, and a button.

The paths are using the Unix forward-slash / convention, so they are cross-platform for macOS. If you're only developing for Windows, you can use \\

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 dist folder.

python
# -*- mode: python ; coding: utf-8 -*-


block_cipher = None


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)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)

exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='hello-world',
          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='hello-world')

If you run the build using this spec file you'll see the icons folder copied across to the dist 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.

Creating a Linux Package (Ubuntu deb)

So far we've used PyInstaller to bundle the application into a Linux executable, along with the associated data files. The output of this bundling process is a folder. However, in order to share this application with other people and allow them to install it, we need to create a Linux package. Packages are distributable files which allow users to install software on their Linux system, as well as setting up things like application entries in the dock/menu.

On Ubuntu (and Debian) packages are named .deb files, on Redhat .rpm and on Arch Linux .pacman. These files are all different formats, but thankfully the process for building them is the same: using a tool named fpm.

In this tutorial we'll work through the steps for creating a Linux package, using an Ubuntu .deb file as an example. However, you will be able to use the same steps for your own system.

Installing fpm

The fpm tool is written in ruby and requires ruby to be installed to use it. Install ruby using your systems package manager, for example.

bash
$ sudo apt-get install ruby

Once ruby is installed, you can install fpm using the gem tool.

bash
$ gem install fpm --user-install

If you see a warning e.g. You don't have /home/martin/.local/share/gem/ruby/2.7.0/bin in your PATH you will need to add that to your path in your .bashrc file.

...and that's it. Once the installation is complete, you're ready to use fpm. You can check it is installed and working by running:

bash
$ fpm --version
1.14.2

Checking your build

In a terminal, change to the folder containing your application source files & run a PyInstaller build to generate the dist folder. Test that the generated build runs as expected (it works, and icons appear) by opening the dist folder in the file manager, and double-clicking on the application executable.

If everything works, you're ready to package the application -- if not, go back and double check everything.

It's always a good idea to test your built application before packaging it. Then if anything goes wrong, you know where the problem is!

Now let's package our folder using fpm.

Structuring your package

Linux files are used to install all sorts of applications, including system tools. Because of this they are set up to allow you to place files anywhere in the Linux filesystem -- and there are specific correct places to put different files. For a bundled package like ours, we can -- thankfully -- put our executable and associated data files all under the same folder (in /opt). However, to have our application show up in the menus/search we'll also need to install a .desktop file under /usr/share/applications.

The simplest way to ensure things end up in the correct location is to recreate the target file structure in a folder & then tell fpm to package using that folder as the root. This process is also easily automatable using a script (see later).

In your projects root folder, create a new folder called package and subfolders which map to the target filesystem -- /opt will hold our application folder hello-world, and /usr/share/applications will hold our .desktop file., while /usr/share/icons... will hold our application icon.

bash
$ mkdir -p package/opt
$ mkdir -p package/usr/share/applications
$ mkdir -p package/usr/share/icons/hicolor/scalable/apps

Next copy (recursively, with -r to include subfolders) the contents of dist/app to package/opt/hello-world -- the /opt/hello-world path is the destination of our application folder after installation.

bash
$ cp -r dist/hello-world package/opt/hello-world

We're copying the dist/hello-world folder. The name of this folder will depend on the application name configured in PyInstaller.

The icons

We've already set an icon for our application while it's running, using the penguin.svg file. However, we want our application to show it's icon in the dock/menus. To do this correctly, we need to copy our application icons into a specific location, under /usr/share/icons.

This folder contains all the icon themes installed on the system, but default icons for applications are always placed in the fallback hicolor theme, at /usr/share/icons/hicolor. Inside this folder, there are various folders for different sizes of icons.

bash
$ ls /usr/share/icons/hicolor/
128x128/          256x256/          64x64/            scalable/
16x16/            32x32/            72x72/            symbolic/
192x192/          36x36/            96x96/
22x22/            48x48/            icon-theme.cache
24x24/            512x512/          index.theme

We're using the scalable folder, since our icon is an SVG (Scalable Vector Graphics). If you're using a specifically sized PNG file, place it in the correct location -- and feel free to add multiple different sizes, to ensure your application icon looks good when scaled. Application icons go in the subfolder apps.

bash
$ cp icons/penguin.svg package/usr/share/icons/hicolor/scalable/apps/hello-world.svg

Name the destination filename of the icon after your application to avoid it clashing with any others! Here we're calling it hello-world.svg.

The .desktop file

The .desktop file is a text configuration file which tells the Linux desktop about a desktop application -- for example, where to fine the executable, the name and which icon to display. You should include a .desktop file for your apps to make them easy to use. An example .desktop file is shown below -- add this to the root folder of your project -- with the name hello-world.desktop, and make any changes you like.

ini
[Desktop Entry]

# The type of the thing this desktop file refers to (e.g. can be Link)
Type=Application

# The application name.
Name=Hello World

# Tooltip comment to show in menus.
Comment=A simple Hello World application.

# The path (folder) in which the executable is run
Path=/opt/hello-world

# The executable (can include arguments)
Exec=/opt/hello-world/hello-world

# The icon for the entry, using the name from `hicolor/scalable` without the extension.
# You can also use a full path to a file in /opt.
Icon=hello-world

For more information on creating .desktop files see this documentation.

Now the hello-world.desktop file is ready, we can copy it into our install package with.

bash
$ cp hello-world.desktop package/usr/share/applications

Permissions

Packages retain the permissions of installed files from when they were packaged, but will be installed by root. In order for ordinary users to be able to run the application, you need to change the permissions of the files created.

We can recursively apply the correct permissions 755 - owner can read/write/execute, group/others can read/execute. and 644, owner can read/write, group/others can red to the contents of our executable folder and icons/desktop files.

bash
$ find package/opt/hello-world -type f -exec chmod 755 -- {} +
$ find package/usr/share -type f -exec chmod 644 -- {} +

Building your package

Now everything is where it should be in our package "filesystem", we're ready to start building the package itself.

Enter the following into your shell.

bash
fpm -C package -s dir -t deb -n "hello-world" -v 0.1.0 -p hello-world.deb

The arguments in order are:

  • -C the folder to change to before searching for files: our package folder
  • -s the type of source(s) to package: in our case dir, a folder
  • -t the type of package to build: a deb Debian/Ubuntu package
  • -n the name of the application: "hello-world"
  • -v the version of the application: 0.1.0
  • -p the package name to output: hello-world-deb

For more command line arguments, see the fpm documentation.

You can create other package types (for other Linux distributions) by changing the -t argument.

After a few seconds, you should see a message to indicate that the package has been created.

bash
$ fpm -C package -s dir -t deb -n "hello-world" -v 0.1.0 -p hello-world.deb
Created package {:path=>"hello-world.deb"}

Installation

The package is ready! Let's install it.

bash
$ sudo dpkg -i hello-world.deb

You'll see some output as the install completes.

python
Selecting previously unselected package hello-world.
(Reading database ... 172208 files and directories currently installed.)
Preparing to unpack hello-world.deb ...
Unpacking hello-world (0.1.0) ...
Setting up hello-world (0.1.0) ...

Once installation has completed, you can check the files are where you expect, under /opt/hello-world

bash
$ ls /opt/hello-world
app                        libpcre2-8.so.0
base_library.zip           libpcre.so.3
icons                      libpixman-1.so.0
libatk-1.0.so.0            libpng16.so.16
libatk-bridge-2.0.so.0     libpython3.9.so.1.0
etc.

Next try and run the application from the menu/dock -- you can search for "Hello World" and the application will be found (thanks to the .desktop file).

Hello world in Ubuntu search Application shows up in the Ubuntu search panel, and will also appear in menus on other environments.

If you run the application, the icons will show up as expected.

Application, running in the dock Application runs and all icons show up as expected.

Scripting the build

We've walked through the steps required to build an installable Ubuntu .deb package from a PyQt5 application. There isn't that much too it, but if you have to do it more than once it'll quickly get quite tedious and prone to mistakes. To avoid problems I recommend scripting this with a simple bash script & fpm own automation tool.

In this section I'll give you scripts that automate the build we've done for our Hello World application.

package.sh

Save in your project root and chmod +x to make it executable.

sh
#!/bin/sh
# Create folders.
[ -e package ] && rm -r package
mkdir -p package/opt
mkdir -p package/usr/share/applications
mkdir -p package/usr/share/icons/hicolor/scalable/apps

# Copy files (change icon names, add lines for non-scaled icons)
cp -r dist/hello-world package/opt/hello-world
cp icons/penguin.svg package/usr/share/icons/hicolor/scalable/apps/hello-world.svg
cp hello-world.desktop package/usr/share/applications

# Change permissions
find package/opt/hello-world -type f -exec chmod 755 -- {} +
find package/usr/share -type f -exec chmod 644 -- {} +

.fpm file

fpm allows you to store the configuration for the packaging in a configuration file. The file name must be .fpm and it must be in the folder you run the fpm tool. Our configuration is as follows.

sh
-C package
-s dir
-t deb
-n "hello-world"
-v 0.1.0
-p hello-world.deb

You can override any of the options you like when executing fpm by passing command line arguments as normal.

Executing the build

With these scripts in place our application can be packaged reproducibly with the commands:

bash
pyinstaller hello-world.spec
./package.sh
fpm

Feel free to customize these build scripts further yourself to suit your own project!

Wrapping up

In this tutorial we've covered how to build your PyQt5 applications into a Linux executable using PyInstaller, including adding data files along with your code. Then we walked through the process of creating a Ubuntu .deb package 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 more, see the complete PyQt5 tutorial.

09 Apr 16:00

PyBites: Code Better with Type Hints – Part 3

Tom Roche

see links to previous parts @ footnotes

This is the third part of a series of articles dealing with the type annotation system in Python, type hints for short. The second part discussed a set of beginner examples and highlighted the benefits of using type hints.

This article series is aimed at newcomers to type hints and wants to help you get started.

In this third part, I will continue discussing slightly more advanced examples to further deepen your knowledge about type hints. Each example will cover a certain topic and look at type hints from a slightly different perspective. Most examples will end with a tip or best practice to help you improve your type hint skills!

Meaningful empty variables

A variable might be initialized as an empty container or None because its actual content is assigned later in the code. For example, this might be the case for the variable holding the return value of a function. Typically, this variable is initialized right at the beginning of a function’s body and its value is assigned multiple times throughout the code. When the function’s return value is correctly type annotated, this will be no problem because you already know the type of the return value thanks to the type hint in the function header (remember the -> annotation?). However, if the return type is not properly annotated, you will not know for sure. Same, if the variable in question is not the return value but some local variable that was neither documented in the function’s docstring nor via an inline comment, you can hardly know what it is going to hold. Let’s assume you encounter the following variable assignment somewhere early in the code:

countries = {}

All you know about the variable countries is that it is a dictionary because it was initialized as such. But you have no clue about what the structure of this dictionary looks like. What is the intended purpose of this variable? Does it hold only countries? Unlikely, because it is a dictionary, so it must be intended to provide some mapping between a key and its value. The interesting question, though, is, what this relationship looks like. What are the keys and what are the values, how complex is this dictionary? You cannot tell until you find the lines of code where the variable is actually assigned a value and being used.

Now, let us help the reader by annotating this variable:

countries: dict[str, tuple[str, int]] = {}

Does this change your understanding of the variable? I hope so! With the proper type hints, you know exactly what to expect from this dictionary: That its keys will be strings (likely the name of the countries) and that its values will be two-element tuples, the first element being a string (maybe the country’s capital) and the second element being an integer (maybe the country’s population). Of course, you will not know what the types stand for just from reading the type hints, but you will gain a better understanding of how this dictionary is structured and how it will be used later in the code. And the fact that its values are tuples tells you something about the values: they are expected to be a fixed (immutable) pair of two elements, so it has to be data that is valid for all countries (like capital and population). In this example, type hints help to reduce ambiguity and uncertainty about the structure and use of variables.

This is the first example where the type annotation is done for a variable outside of a function definition. In fact, you can type annotate everything in Python, not only function parameters. However, as you interact more often with functions or methods than with their actual implementation, type hints are most valuable at places that are often visited.

Nevertheless, let me give you some more examples about variable type annotations outside of functions that might be useful.

class MyClass:
    # You can optionally declare instance variables in the class body
    attr: int
    ...

Here the type hint is useful given that the actual value for attr might be set much later, either in the __init__() method or even later. Before type hints, we used to put the type hint in a comment, but as comments are ignored by static type checkers and are not standardized, it is much better to use a type hint as that is its purpose.

Type hints are even more useful in combination with a dataclass:

from dataclasses import dataclass

@dataclass
class Point:
  x: int
  y: int

Although Python does not prevent you from initializing a Point instance with the wrong types, so Point("1", "2") will not throw an error, the dataclass decorator will use the type hint information for some internal magic like auto-generating a __init__(self, x: int, y: int) method with the right type annotations for each parameter. You can even use typing.ClassVar to express that a variable is a class variable instead of an instance variable. In this case, again, Python does not prevent you from accessing this variable through an instance (p.points instead of Point.points) but it will prevent the class variable from being added to the parameter list of the __init__ method!

from dataclasses import dataclass
from typing import ClassVar

@dataclass
class Point:
  x: int
  y: int
  points: ClassVar = 0

Meaningful type aliases

By now, you have seen a fair amount of type hints and I have to admit that readability is not always improved when types get more complicated and thus longer type hints are necessary, most of all with nested and multiple types. So, instead of accepting ever longer type hints, you can improve both readability and meaning a lot by introducing your own type names. This is possible because type annotations are just Python objects and can be assigned to variables.

Look at the following example:

Vector = List[float]
Matrix = List[Vector]
Scalar = float

def dot(v: Vector, w: Vector) -> Scalar:
  """Computes the dot product of two vectors."""
  ...

def magnitude(v: Vector) -> Scalar:
  """Returns the magnitude (or length) of a vector."""
  ...

def shape(A: Matrix) -> tuple[int, int]:
  """Returns (no. rows, no. columns) of a matrix."""
  ...

def identity_matrix(n: int) -> Matrix:
  "Returns the n x n identity matrix."""
  ...

By introducing the speaking names Vector, Matrix and Shape as new types, the type hints not only provide information about their parameters but also inform about the domain and build a coherent domain language. Now you are able to think of the dot function no longer as a function that takes two list of floats and produces a single number but as an operation between two vectors that produces a scalar. And the magnitude of a vector is, again, a scalar, representing its length. And because the shape of a matrix has two elements it follows that a matrix must be some object that has two dimensions.

By assigning new and meaningful names to existing type definitions you can further increase the readability and comprehensibility of your code and better communicate your intends to the reader.

Update for Python 3.10: Since Python 3.10 (and thanks to PEP 613) it is possible to use the new TypeAlias from the typing module to explicitly declare a type alias. This solves some problems with the old syntax, which, strictly speaking, is a global expression and is only interpreted as type alias if used correctly. With TypeAlias , you can rewrite the example to

from typing import TypeAlias

Vector: TypeAlias = list[float]
Matrix: TypeAlias = list[Vector]
Scalar: TypeAlias = float

This requires an additional import, which might feel like a burden, but it increases readability, clearly states your intent and improves the capability of static type checkers.

Avoid confusion about multiple types

If you need to allow multiple types, you can use typing.Union, e.g. Union[str, int] which tells the reader that a variable can be either a string or an integer. This might be useful whenever it is not possible to require a single type or you want to provide some convenience to the users of your function or class. However, it is best to avoid multiple types in favor of clarity and to use other mechanism like single dispatch or a more general variable type like a dictionary.

Look at the following example:

def send_email(address: Union[str, List[str]], body: str):
  ...

It seems that the authors of the send_email function wanted to be less strict about the type of the address parameter. Instead of always requiring it to be a list, they also foresaw the possibility or necessity to allow a single string. We can immediately understand this type hint as follows: When there is only a single address you want to send an email to, you can pass this address to the function as it is, but if you have multiple addresses, you can also pass the whole list and the function will take care of it. Nice to have this information directly encoded in a type hint, isn’t it?

If you wonder how your favorite editor handles the case of multiple types, at least for VSC I can tell you that it’s clever and suggests all methods for both strings and lists. Not what I was expecting but certainly appreciated!

Avoid confusion about None

None is one of our best friends and can be really nasty at the same time! It is best practice to initialize optional parameters with a default value of None so you can check against None before you use the attribute and avoid the problem of some nasty bugs, e.g. when using an empty list as default (see [7]). However, this does not prevent the user of your function from passing None to any of the parameters, even the required ones! This happens regularly, mostly when chaining functions and passing through return values. So what can you do to spot this? Use typing.Optional when you want to allow None, otherwise it will be a type error.

Let’s return to the example of sending an email, this time a little mote elaborated:

def send_email(
    address: Union[str, List[str]],
    sender: str,
    cc: Optional[List[str]],
    bcc: Optional[List[str]],
    subject="",
    body: Optional[List[str]]=None,
) -> bool:
    ...

There are a few parameters that have an Optional type hint, namely cc, bcc and body. With no type hints at all, a static type check would not give you any error when passing None to any of the parameters, because by default a parameter without a type hint has the type Any, which is also fulfilled by None. When adding a type hint without Optional, None is no longer a valid option and your type checker will inform you about the incompatible type. So for example when passing None as value for sender you get: Argument 2 to send_email has incompatible type None; expected str.

To explicitly allow for None you can add Optional to the type hint, which allows both the actual type and the special value None. Now, calling the function with None for the body parameter is not a problem any more because Optional[List[str]] is a shortcut for Union[List[str], None] and thus allows for None values.

A slightly different example is the following: Imagine you want to call the max function with some list of numbers. However, some of the numbers might actually be None values due to some error during the recording process (e.g. sensor values). This is a problem because the max function does not handle None values well and instead throws a TypeError.

>>> max([1,None,3])
TypeError: '>' not supported between instances of 'NoneType' and 'int'

This is hard to see without a static type checker. However, if there is a variable list_of_numbers that is correctly annotated with List[Optional[float]], indicating that some numbers might be missing, Pylance will inform you about a problem with passing this variable to the max function: “No overloads for max match the provided arguments Argument types: (list[float | None]).

VSC max error

So type hints successfully prevented you from running into a runtime error because of a hidden None value in some of the attributes.

This concludes the third part of this series. You should have learned about annotating not yet fully initialized variables, instance and class variables and how to deal with multiple types at once as well as the special None value. I think this are enough examples, so the next part of this series will deal with how to actually check type hints.


I hope you have enjoyed reading the article! If you have any questions, comments or suggestions, don’t hesitate to contact me via the PyBites Slack, LinkedIn or GitHub. You can also leave your comment right on this page.

Let me know if you want to read more about this topic or any other topic, for that matter.

Resources

Resources are covering the complete article series about type hints. Not all resources were referenced in this part.

[1] PEP 484 — Type Hints

[2] PEP 483 — The Theory of Type Hints

[3] Python Type Checking (Guide) — Real Python

[4] Typing module documentation

[5] Mypy documentation

[6] Typing cheat sheet – Pysheeet

[7] Null in Python: Understanding Python’s NoneType Object — Real Python

09 Apr 02:50

Michael and Us: Akira, the Wrath of God

Tom Roche

MU 321: excellent as usual, though the movie review (at end) is somewhat shorter than usual

Luke and Will discuss AKIRA (1988), the groundbreaking anime classic. We hash over the film's vision of a future-dystopia, finding elements both unique to 1988 and applicable to all times. PLUS: the new Amazon union in Staten Island, and checking the pulse of right-wing politics on Canada.


Michael and Us is a podcast about political cinema and our crumbling world hosted by Will Sloan and Luke Savage.



Our GDPR privacy policy was updated on August 8, 2022. Visit acast.com/privacy for more information.

08 Apr 16:25

617 - Smile Factory feat. Chris Smalls (4/7/22)

Tom Roche

Starts with excellent interview with Christian Smalls (head of Amazon Labor Union) on their recent victory in organizing JFK8 distribution center. Then Boyz bant on how Elon Musk should remake Twitter after his also-recent (apparent) takeover: by CTH standards, this is good but not great. Similarly, they discuss how groomspeak and pedophilia accusations now dominate US rightwing discourse: they excellently dissect Jim "Gym" Jordan's pedophile past (and briefly revive-to-crucify Dennis Hastert, while somehow forgetting Larry Craig's famously "wide stance"). Unfortunately their long drag on Kylee Zempel (of The Federalist) and her retrograde takes on gender, sexuality, and beauty ... tends to drag a bit long. Zempel (alumna of Maranatha Baptist University--no lie!) is deserving of ridicule, and the Boyz bring it, but it's unfortunately not their best.

Will talks to president of the Amazon Labor Union Chris Smalls about the successful effort to unionize the JFK8 Amazon fulfillment center on Staten Island. Then, the boys discuss the current rash of conservative outrage over gender and sexuality, and Felix makes a plea for special exemptions from new Twitter board member Elon Musk.

07 Apr 16:45

Seismology

Tom Roche

note 1st BBC {Radio 4 broadcast, Sounds availability} 10 Mar 2022

Melvyn Bragg and guests discuss the study of earthquakes. A massive earthquake in 1755 devastated Lisbon, and this disaster helped inspire a new science of seismology which intensified after San Francisco in 1906 and advanced even further with the need to monitor nuclear tests around the world from 1945 onwards. While we now know so much more about what lies beneath the surface of the Earth, and how rocks move and crack, it remains impossible to predict when earthquakes will happen. Thanks to seismology, though, we have a clearer idea of where earthquakes will happen and how to make some of them less hazardous to lives and homes.

With

Rebecca Bell Senior lecturer in Geology and Geophysics at Imperial College London

Zoe Mildon Lecturer in Earth Sciences and Future Leaders Fellow at the University of Plymouth

And

James Hammond Reader in Geophysics at Birkbeck, University of London

Producer: Simon Tillotson

07 Apr 13:53

Democracy Now! 2022-04-04 Monday

Tom Roche

EXCELLENT Amazon/ALU segment (with Christian Smalls, Derrick Palmer, and Josefa Velásquez)

Democracy Now! 2022-04-04 Monday

  • Headlines for April 04, 2022
  • "We Just Unionized Amazon": How Two Best Friends Beat the Retail Giant's Union-Busting Campaign
  • Mass Graves in Kyiv Suburb of Bucha; Amnesty Int'l Documents Unlawful Killing of Civilians in Ukraine

Download this show

06 Apr 02:46

Message of the Week w/Special Guest Alex Fumero

Tom Roche

Fumero (and Josh and Dave) quite funny

Once again, we dive into the surreal universe where Republicans are hard to beat in California, and Democrats have a lock on the Latino vote.
06 Apr 02:40

Bonus: Opiate Whack-a-Mole feat. Zachary Siegel

Tom Roche

good example of Chapo being serious

Felix sits down with writer Zachary Siegel to discuss the opiate crisis, the tainted drug supply in America, and the minefield of federal drug policy including the much hated “opiate whack-a-mole” that the government plays.

Find Zach here:

https://twitter.com/ZachWritesStuff

https://tanag.substack.com/