Understand the requirements to create your own Python package.
Packaging the application
To distribute the application to users we have to package it. We
are going to use the setuptools
library.
Currently, the project can only be run as a script directly, that is, by running the files directly with python3 /path/to/simplemdviewer_app.py. This is not a convenient way to run a desktop application.
The goal in this page is to make the project available as a console script and as a module:
You'll know it is a console script when you are able to run it with simplemdviewer
You'll know it is a module when you are able to run it with python3 -m simplemdviewer
If you'd like to learn more about Python packaging, you'll be interested in the
Python Packaging User Guide.
General structure
Let's recapitulate what the file structure of the project should be:
simplemdviewer
├── README.md
├── LICENSE.txt
├── MANIFEST.in # To add our QML file to the Python module
├── pyproject.toml # The main file to manage the project
├── org.kde.simplemdviewer.desktop
├── org.kde.simplemdviewer.json
├── org.kde.simplemdviewer.svg
├── org.kde.simplemdviewer.metainfo.xml
└── src/
├── __init__.py # To import the src/ directory as a package
├── __main__.py # To signal simplemdviewer_app as the entrypoint
├── simplemdviewer_app.py
├── md_converter.py
└── qml/
└── main.qml
[build-system]requires=["setuptools"]build-backend="setuptools.build_meta"[project]name="org.kde.simplemdviewer"version="0.1"authors=[{name="Example Author",email="example@author.org"}]maintainers=[{name="Example Author",email="example@author.org"}]description="A simple markdown viewer"classifiers=["Development Status :: 5 - Production/Stable","License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)","Intended Audience :: End Users/Desktop","Topic :: Utilities","Programming Language :: Python","Operating System :: POSIX :: Linux",]keywords=["viewer converter markdown"]urls={Homepage="https://mydomain.org/simplemdviewer"}dependencies=["markdown"][project.readme]file="README.md"content-type="text/markdown"[project.scripts]simplemdviewer="simplemdviewer.simplemdviewer_app:main"[tool.setuptools]packages=["simplemdviewer"]package-dir={simplemdviewer="src"}include-package-data=true[tool.setuptools.data-files]"share/applications"=["org.kde.simplemdviewer.desktop"]"share/icons/hicolor/scalable/apps"=["org.kde.simplemdviewer.svg"]"share/metainfo"=["org.kde.simplemdviewer.metainfo.xml"]
Don't worry about the details for now. We will revisit each part as necessary.
The following four sections of the pyproject.toml are fairly straightforward:
[build-system] tells Python to fetch and use setuptools to build the project
[project] contains the general metadata for the project
[project.readme] specifies a default README file for the project in Markdown
[tool.setuptools.data-files] mentions where setuptools should install additional data files that are not typically present in a Python package
Note
Previously, this tutorial used a combination of setup.py to initialize setuptools,
setup.cfg to manage metadata and package information, and pyproject.toml
to specify dependencies. However, PEP 621
has defined pyproject.toml as the main way to specify a project's metadata,
and setuptools has transitioned to
using pyproject.toml as a declarative way to define package information.
It is no longer necessary to initialize setuptools with a setup.py script,
and all metadata, package information, and dependencies are listed
in the pyproject.toml instead.
App metadata
Let's start with the metadata first so we can get it out of the way, namely the ones listed in the sections [project.readme] and [tool.setuptools.data-files].
Create a simplemdviewer/README.md:
1
2
3
# Simple Markdown Viewer
A simple Markdown viewer created with Kirigami, QML and Python.
Another important piece is the license of our project. Create a
simplemdviewer/LICENSE.txt and add the text of the
license of our project.
<?xml version="1.0" encoding="utf-8"?><componenttype="desktop"><id>org.kde.simplemdviewer</id><metadata_license>CC0-1.0</metadata_license><project_license>GPL-3.0+</project_license><name>Simple Markdown Viewer</name><namexml:lang="ca">Visualitzador senzill de Markdown</name><namexml:lang="cs">Jednoduché prohlížení souborů Markdown</name><namexml:lang="eo">Simpla Markdown-Vidilo</name><namexml:lang="es">Sencillo visor de Markdown</name><namexml:lang="fr">Afficheur simple pour langage « Markdown »</name><namexml:lang="it">Visore Markdown semplice</name><namexml:lang="ja">シンプルな Markdown ビューアー</name><namexml:lang="nl">Eenvoudige Markdown-viewer</name><namexml:lang="sk">Jednoduchý prehliadač markupu</name><namexml:lang="sl">Enostavni ogledovalnik Markdown</name><namexml:lang="sv">Enkel Markdown-visning</name><namexml:lang="tr">Basit Markdown Görüntüleyicisi</name><namexml:lang="uk">Простий переглядач Markdown</name><namexml:lang="x-test">xxSimple Markdown Viewerxx</name><namexml:lang="zh-TW">簡單 Markdown 檢視器</name><summary>A simple markdown viewer application</summary><summaryxml:lang="ca">Una aplicació senzilla de visualització de Markdown</summary><summaryxml:lang="eo">Simpla markdown-spektila aplikaĵo</summary><summaryxml:lang="es">Una sencilla aplicación de visor markdown</summary><summaryxml:lang="fr">Une application d'afficheur simple pour langage « Markdown »</summary><summaryxml:lang="it">L'applicazione di un visore Markdown semplice</summary><summaryxml:lang="ja">シンプルな markdown ビューアーアプリケーション</summary><summaryxml:lang="nl">Een eenvoudige toepassing als Markdown-viewer</summary><summaryxml:lang="sl">Aplikacija enostavnega ogledovalnika Markdown</summary><summaryxml:lang="sv">Ett enkelt Markdown-visningsprogram</summary><summaryxml:lang="tr">Basit bir Markdown görüntüleyici uygulama</summary><summaryxml:lang="uk">Проста програма для перегляду Markdown</summary><summaryxml:lang="x-test">xxA simple markdown viewer applicationxx</summary><summaryxml:lang="zh-TW">一個簡單的 Markdown 檢視器應用程式</summary><description><p>Simple Markdown Viewer is a showcase application for QML with Python development</p><pxml:lang="ca">El visualitzador senzill de Markdown és una aplicació per a presentar el desenvolupament del QML amb el Python</p><pxml:lang="eo">Simple Markdown Viewer estas montra aplikaĵo por QML kun Python-evoluo</p><pxml:lang="es">Sencillo visor de Markdown es una aplicación de demostración para QML con desarrollo en Python</p><pxml:lang="fr">Un afficheur simple pour langage « Markdown » est une application majeure pour QML pour les développement sous Python</p><pxml:lang="it">Visore Markdown semplice è un'applicazione dimostrativa per QML nello sviluppo in Python</p><pxml:lang="ja">シンプルな Markdown ビューアーは Python 開発による QML のサンプルアプリケーションです。</p><pxml:lang="nl">Eenvoudige Markdown-viewer is een uitstelkast voor QML met Python ontwikkeling</p><pxml:lang="sl">Enostavni ogledovalnik Markdown je predstavitvena aplikacija za razvoj QML s Pythonom</p><pxml:lang="sv">Enkel Markdown-visning är ett förevisningsprogram för QML med Python-utveckling</p><pxml:lang="tr">Basit Markdown Görüntüleyicisi, Python ile QML geliştirmek için bir vitrin uygulamadır</p><pxml:lang="uk">Проста програма для перегляду Markdown є прикладом програми для розробки з QML за допомогою Python</p><pxml:lang="x-test">xxSimple Markdown Viewer is a showcase application for QML with Python developmentxx</p><pxml:lang="zh-TW">《簡單 Markdown 檢視器》是用來示範 QML 與 Python 併用開發的應用程式</p></description><urltype="homepage">https://mydomain.org/simplemdviewer</url><releases><releaseversion="0.1"date="2022-02-25"><description><p>First release</p></description></release></releases><provides><binary>simplemdviewer</binary></provides></component>
For this tutorial the well known Markdown icon is okay.
Get the
Markdown
icon and save it as simplemdviewer/org.kde.simplemdviewer.svg:
We need the icon to be perfectly squared, which can be accomplished with
Inkscape:
Open org.kde.simplemdviewer.svg in Inkscape.
Type Ctrl+a to select everything.
On the top W: text field, type 128 and press Enter.
Go to File -> Document Properties...
Change the Width: text field to 128 and press Enter.
Save the file.
Code changes
Before creating a console script or module, we first need to turn our existing code into a package.
Create an empty simplemdviewer/src/__init__.py file.
This file just needs to be present in order to import a directory as a package.
touch __init__.py
Usually, Python packages keep their package source code directly in the root folder. Since we are using a custom package directory, namely src/, we pass it as the correct package-dir for our simplemdviewer application in the pyproject.toml:
Here, the package name is simplemdviewer, and its source code is located in simplemdviewer/src/ instead of the root folder. Now that we have a package name, we can use that for the way we import modules in our code.
Update simplemdviewer/src/simplemdviewer_app.py to:
#!/usr/bin/env python3importosimportsysimportsignalfromPySide6.QtGuiimportQGuiApplicationfromPySide6.QtCoreimportQUrlfromPySide6.QtQmlimportQQmlApplicationEngine# from md_converter import MdConverterfromsimplemdviewer.md_converterimportMdConverter# noqa: F401defmain():"""Initializes and manages the application execution"""app=QGuiApplication(sys.argv)engine=QQmlApplicationEngine()"""Needed to close the app with Ctrl+C"""signal.signal(signal.SIGINT,signal.SIG_DFL)"""Needed to get proper KDE style outside of Plasma"""ifnotos.environ.get("QT_QUICK_CONTROLS_STYLE"):os.environ["QT_QUICK_CONTROLS_STYLE"]="org.kde.desktop"base_path=os.path.abspath(os.path.dirname(__file__))url=QUrl(f"file://{base_path}/qml/main.qml")engine.load(url)iflen(engine.rootObjects())==0:quit()app.exec()if__name__=="__main__":main()
#!/usr/bin/env python3importosimportsysimportsignalfromPyQt6.QtGuiimportQGuiApplicationfromPyQt6.QtCoreimportQUrlfromPyQt6.QtQmlimportQQmlApplicationEngine,qmlRegisterType# from md_converter import MdConverterfromsimplemdviewer.md_converterimportMdConverterdefmain():"""Initializes and manages the application execution"""app=QGuiApplication(sys.argv)engine=QQmlApplicationEngine()"""Needed to close the app with Ctrl+C"""signal.signal(signal.SIGINT,signal.SIG_DFL)"""Needed to get proper KDE style outside of Plasma"""ifnotos.environ.get("QT_QUICK_CONTROLS_STYLE"):os.environ["QT_QUICK_CONTROLS_STYLE"]="org.kde.desktop"qmlRegisterType(MdConverter,"org.kde.simplemdviewer",1,0,"MdConverter")base_path=os.path.abspath(os.path.dirname(__file__))url=QUrl(f"file://{base_path}/qml/main.qml")engine.load(url)iflen(engine.rootObjects())==0:quit()app.exec()if__name__=="__main__":main()
Create a __main__.py file in the simplemdviewer/src/ directory:
This simply adds the contents of the current directory (src/) and imports it as a module named simplemdviewer_app, then immediately run the main() function of the application.
Make sure that you have a project script in your pyproject.toml:
Now the application should run as an executable package and as a module.
Running directly, as a module, and as a console script
When you run the script directly with python3 src/simplemdviewer_app.py, under the hood you are first running __main__. If the same script were imported instead of run, it would be running simplemdviewer_app, the name of the module. The if condition is there so main() will only run when the script is run, not every time it is imported by another script:
34
35
if__name__=="__main__":main()
That's it for running scripts directly. On top of that, we use setuptools to specify the package:
We have specified a package called simplemdviewer in line 31 whose source code is found in simplemdviewer/src/ in line 32. This is necessary because we don't want to call the application as simplemdviewer_app every time for either module or console script, we want the friendlier name simplemdviewer.
Now, to allow the application to run as a module like in python3 -m simplemdviewer, all we need to do is have a __main__.py to the same folder where the code is:
When attempting to run the package simplemdviewer as a module, setuptools will simply look for a __main__.py, which in turn points to the main() function in our simplemdviewer_app.py:
12
13
14
15
defmain():"""Initializes and manages the application execution"""app=QGuiApplication(sys.argv)engine=QQmlApplicationEngine()
Under the hood of the module, __name__ is actually set to simplemdviewer_app unlike with when running the script directly, but main() is executed anyway.
Note
setuptools is not necessarily involved in the process of running a script directly or running a module as a script. However, in the case of modules, it specifies the package, which in turn matters for the module name.
In line 28, we specify a project script, namely the entrypoint to run the application. This is functionality provided directly by setuptools. In this case it's a console script because it can run on a terminal; a GUI script would always run without a terminal, but this only matters on Windows. A project script is simply a wrapper that setuptools creates on top of the application so it can be run easily like an executable.
We want that, when attempting to run the command simplemdviewer in the terminal, setuptools searches inside the simplemdviewer package for the module simplemdviewer_app, and runs the function main() directly:
12
13
14
15
defmain():"""Initializes and manages the application execution"""app=QGuiApplication(sys.argv)engine=QQmlApplicationEngine()
Doing things this way, we bypass the __main__ check entirely.
This is how the console script is created.
In other words:
Method
Tool
Runs with
run directly
->
Python
->
__main__
->
main()
python3 simplemdviewer_app.py
module
->
Setuptools
->
__main__.py
->
main()
python3 -m simplemdviewer
console script
->
Setuptools
->
project script
->
main()
simplemdviewer
Running the app
From inside the simplemdviewer/ directory, we can install it in development mode and then run it:
python3 -m pip install --editable .
# As a modulepython3 -m simplemdviewer
# As a console scriptsimplemdviewer
If you have put the required files in the right places, running the application
as a module and as a console script should work.
Now that we know it works, let's generate the package for our program.
Make sure that the latest version of build is installed:
python3 -m pip install --upgrade build
From inside the simplemdviewer/ directory, run:
python3 -m build
As soon as the build completes, two archives will be created in the
dist/ directory:
The org.kde.simplemdviewer-0.1.tar.gz source archive
The org.kde.simplemdviewer-0.1-py3-none-any.whl package ready for
distribution in places such as PyPI
To test the application properly, we can try installing it in a clean virtual environment that has PySide/PyQt:
# As a modulepython3 -m simplemdviewer
# As a console scriptsimplemdviewer
At this point we can tag and release the source code. Linux distributions will
package it and the application will be added to their software repositories.