Content from What is QMI?


Last updated on 2025-08-25 | Edit this page

Overview

Questions

  • What is QMI?

Objectives

  • Explain what is QMI and its main features

What is QMI?


QMI is a Python 3 framework for controlling laboratory equipment. It is suitable for anything ranging from one-off scientific experiments to robust operational setups.

QMI is developed by QuTech to support advanced physics experiments involving quantum bits. However, other than its name and original purpose, the_re is nothing specifically quantum about QMI — it is potentially useful in any environment where monitoring and control of measurement equipment is needed. It is also multi-platform. At QuTech, QMI is regularly used in both Linux and Windows, and running QMI on macOS is also possible.

It supports instruments and devices that encapsulate equipment under computer control. A number of instruments are provided out of the box, and it is relatively easy to add your own. QMI makes use of tasks that can encapsulate a (background) process that needs to run temporarily or indefinitely. It offers network transparency; instruments and tasks can be remotely started, stopped, monitored and controlled. With these features it can be used as basis for monitoring and control of complicated setups, distributed over multiple locations.

Device drivers


With device drivers instruments and devices can be controlled. Device drivers are importable classes in QMI with (a selection of) functions with which the device can be controlled and monitored. QMI has currently device drivers for 45 manufacturers and 80 devices. In the picture below you can see most of the supported instruments in QMI.

Montage of QMI drivers manufacturer and devices
Montage of QMI drivers manufacturer and devices

QMI contexts


When using QMI in a Python program, a context is started. While the program is running, this context contains all information about the device drivers and tasks added in the context in the program. One of the main features of QMI is that these contexts are also approachable from another QMI context, and the instruments and tasks in the context can be controlled through another context, see also Figure 1. Advantages of this are that instruments can be grouped in logical groups in separate contexts and there is therefore no need to make one big program that contains all the instruments, tasks and whatever logic or timing is necessary. The programs can that way be made modular, with easy addition or removal of contexts.

Figure 1: An example of three QMI contexts where two instrument device drivers are added to QMI context 1. Then we have context 2, that runs a task, which is configured to make connection to context one, and to control the instruments in it. This second contexts now sends also out settings and status signals which can e.g. be forwarder to a database. A third context monitors the task status in context two and instrument status in context one. This context is hooked in the status signal and at specific signal values or circumstances could either tell context two to change settings or stop task, or send specific commands to the instruments in context one.
Figure 1: An example of three QMI contexts where two instrument device drivers are added to QMI context 1. Then we have context 2, that runs a task, which is configured to make connection to context one, and to control the instruments in it. This second contexts now sends also out settings and status signals which can e.g. be forwarder to a database. A third context monitors the task status in context two and instrument status in context one. This context is hooked in the status signal and at specific signal values or circumstances could either tell context two to change settings or stop task, or send specific commands to the instruments in context one.

Remote Procedure Calls


Second main feature of QMI is its Remote Procedure Call (RPC) protocol that enables communication between computers in the same network. The three contexts in Figure 1 could reside in three different PCs and communicate with each other with the help of the RPC protocol and QMI signals. The user only needs to setup QMI configuration files for these contexts, where information about other contexts are present. That way the contexts know where to look for other contexts. It is possible to also look for contexts not defined in the configuration files, but more about that later.

QMI signals


The last main feature to mention is the QMI signals. The device drivers and tasks can be equipped with signal publishers and receivers. These can be used e.g. to send simple triggering signals between tasks or devices, or to send data to some external party. They can become handy with tasks that need to wait for some event to occur before performing some specific action, and thus wait to receive a signal from a publisher. Or if some data needs to be (periodically) saved to a database, like InfluxDB or our own Quantum Data Lake, for later analysis. The signals have been also used for remote live monitoring by sending data to a Grafana server.

Key Points
  • Device drivers: With device drivers instruments and devices can be controlled
  • QMI contexts: A QMI context envelops instruments, tasks and other RPC objects and can be connected to from outside from another QMI context
  • Remote Procedure Calls: Custom RPC are used to control objects in QMI contexts
  • QMI signals: QMI context objects can also broadcast and receive signals, which can include e.g. data

Content from 'Hello World'


Last updated on 2025-08-22 | Edit this page

Overview

Questions

  • How are the QMI contexts created?

Objectives

  • Create a simple QMI context

“Hello world” context creation


Start up python

python

We are now in an interactive Python shell. Let’s start with importing QMI.

PYTHON

import qmi

Presuming you had just installed QMI with Pip, this should just work without any issues. Let’s continue to creating a simple context called “hello_world”.

PYTHON

qmi.start("hello_world", config_file=None)

Now, your first QMI context has been started. You can verify this with

PYTHON

qmi.context()

OUTPUT

QMI_context(name='hello_world')

A couple of remarks are in place already here. The context name is with underscore (_) as spaces in the context names cause issues. So, avoid those and also special letters like ‘!’, ‘@’, ‘#’, ‘$’ etc. We also gave a second input parameter config_file=None for the call. This was actually optional as the default value for the parameter is None, but we wanted to illustrate with this that we start a context without specifying a QMI configuration file. In that case, QMI will create a simple configuration for the context. Now just let’s stop the “hello_world” context.

PYTHON

qmi.stop()

We can confirm that the context has been stopped

PYTHON

qmi.info()

OUTPUT

'*** No active QMI context ***'

And trying to a command like qmi.context() now will give an exception. Note that it is important always to stop your contexts. Python does not always manage to clean up the QMI contexts properly, especially when the context has plenty of things going on, if the context is not manually stopped before. This can leave bogus QMI contexts running on your system with e.g. some kind of task or instrument control active.

Key Points
  • A QMI context is created using qmi.start("<context_name>") call
  • Always remember to stop the context with qmi.stop()

Content from Controlling an instrument


Last updated on 2025-08-25 | Edit this page

Overview

Questions

  • How do I control an instrument?

Objectives

  • Add an instrument into a QMI context and control it with RPC commands

Using QMI to control one instrument


In this example we show how to create an instrument object in a context. For this, we have a dummy instrument in the QMI instruments to illustrate. See also the QMI readthedocs documentation about the dummy instrument class call interface. Let’s import the device driver.

PYTHON

from qmi.instruments.dummy.noisy_sine_generator import NoisySineGenerator

So we imported the QMI device driver class called NoisySineGenerator from QMI instrument “manufacturer” dummy and device noisy_sine_generator module. To use this, we start a new QMI context and “make” an instrument object from the device driver.

PYTHON

qmi.start("nsg_demo", None)
nsg = qmi.make_instrument("nsg", NoisySineGenerator)

Now we have an instrument object nsg present in Python. The instrument is also added into the context. This can be checked with:

PYTHON

qmi.show_instruments()

OUTPUT

address       type
------------  ------------------
nsg_demo.nsg  NoisySineGenerator
------------  ------------------

The address “nsg_demo.nsg” means that now there is an instrument object “nsg” present in context “nsg_demo”. The type confirms the instrument object is of expected class type. Alternative way to confirm this is simply to type the object in Python.

PYTHON

nsg

OUTPUT

<rpc proxy for nsg_demo.nsg (qmi.instruments.dummy.noisy_sine_generator.NoisySineGenerator)>

As can be seen, the created object is actually an RPC proxy of the actual instrument object. This has a couple of consequences: The first is that the proxy object can be now shared through the context with other contexts, allowing remote control of the instrument. The second is that the proxy object does not have the full class interface of the device driver, but only the variables that are present in QMI proxy class and functions that have been selected to be RPC callable. We can list the variables of the object with

PYTHON

help(nsg)

It prints out the class docstring, a listing of its callable RPC methods, signals and class constants.

Here are useful information, like the docstring which is the documentation string of the class object. Then all entries listed as “RPC methods” are the callable RPC functions of the object, with their expected input parameters and return value type. A few methods related to the proxy locking (lock, unlock, is_locked and force unlock are not present, though. We also won’t handle these methods in the course, but you can have a look at the tutorial. You see also empty listings “signals” and “constants”, but for this instrument class there are none present.

Anyhow, in the printed out listing, a lot of useful methods are present and supported through the proxy. Let’s try one:

PYTHON

nsg.get_sample()

OUTPUT

96.18801232566346

So we get returned one sample of the sine wave at a random moment. Let’s do for demonstration purpose a little for loop print out the sine wave of our generator “instrument”:

PYTHON

import time
for i in range(1000):
    print(" " * int(40.0 + 0.25 * nsg.get_sample()) + "*")
    time.sleep(0.01)

Nice huh? Let’s then close this context with qmi.stop() and exit Python with exit()and prepare for next example.

Key Points
  • Instruments can be added into contexts with <instrument_object> = qmi.make_instrument("<name>", <ClassName>, <possible_extra_parameters>)
  • Instrument class description can be seen with help(<instrument_object>)
  • Detailed information about the object and variables can be obtained with dir(<instrument_object>)
  • The returned instrument object is an RPC proxy object of the actual class object

Content from Configuring and logging


Last updated on 2025-08-22 | Edit this page

Overview

Questions

  • What are QMI configuration and log files?

Objectives

  • Know how to create a basic configuration file.

QMI Configuration and log file


Many aspects of QMI are configurable via a configuration file. The syntax of this file is very similar to JSON, but unlike JSON, the configuration file may contain comments starting with a # character. By default, QMI attempts to read the configuration from a file named qmi.conf in the home directory (i.e. ‘/home/’ in Linux or ‘C:<user_name>’ folder on Windows). If you want to use a different file name or location, you can specify the configuration file path either as the second argument of qmi.start() or in the environment variable QMI_CONFIG. Let’s create a configuration file qmi.conf at home directory with the following contents:

JSON

{
    # Log level for messages to the console.
    "logging": {
        "console_loglevel": "INFO"
    }
}

This configuration file changes the log level for messages that appear on your terminal. By default, QMI prints only warnings and error messages. Our new configuration also enables printing of informational messages. For further details about logging options, see documentation on qmi.core.logging_init module. Test the new configuration file in a new Python session:

PYTHON

import qmi
qmi.start("hello_world")
qmi.stop()

Notice that we do not pass a ‘None’ as the second argument to qmi.start(). As a result, QMI will try to read the configuration file from its default location. If the configuration file is found and written correctly, QMI should print a bunch of log messages after the call to qmi.start(). If your configuration file is not in the default location, you may have to specify its location with the config_file= argument to qmi.start(). At your home directory should also now be a file called qmi.log. The file works as a log for QMI programs. You will find in this file a log of actions you took until now while testing your first examples. The logging can be disabled by giving input parameter init_logging=False for qmi.start(). We will add more settings to the configuration file as we progress through this tutorial.

Key Points
  • qmi.conf has a JSON-like structure
  • Log levels are set within “logging” keyword section
  • Default name and location of the log file are qmi.log and the user’s home directory

Content from Accessing an instrument remotely


Last updated on 2025-08-22 | Edit this page

Overview

Questions

  • How can we access the instruments remotely?

Objectives

  • Learn to connect between QMI contexts and control instruments over contexts

Accessing a remote instrument


QMI makes it easy to access an instrument instance that exists in another Python program. The programs may even run on different computers. The Python program that contains the instrument instance must be accessible via the network. This can be achieved by extending the QMI configuration file. The new file will look as follows:

JSON

{
    # Log level for messages to the console.
    "logging": {
        "console_loglevel": "INFO"
    },
    "contexts": {
        # Testing remote instrument access.
        "instr_server": {
            "host": "127.0.0.1",
            "tcp_server_port": 40001
        }
    }
}

Note that JSON is very picky about the use of commas. There must be a comma between multiple elements in the same group, but there may not be a comma after the last element of a group.

Start the instrument server with the following lines:

PYTHON

from qmi.instruments.dummy.noisy_sine_generator import NoisySineGenerator
qmi.start("instr_server")
nsg = qmi.make_instrument("nsg", NoisySineGenerator)

Because the name of the context instr_server matches the name specified in the configuration file, QMI opens a TCP server port for this context. Check that the reading of configuration file succeeded by calling qmi.context().get_context_config(). In the response string, the host and tcp_server_port values should be the same as in the configuration file. If this is not the case, stop the context, and start it again providing the qmi.conf file path with the config_file parameter. Then try again to confirm the host and port. Other Python programs can now connect to this port to access the sine generator.

To try this, leave the instrument server session running and start another Python session in a separate terminal window:

PYTHON

import qmi
qmi.start("random_client")
qmi.context().connect_to_peer("instr_server")
nsg = qmi.get_instrument("instr_server.nsg")
nsg.get_sample()

NOTE: With Windows, the connect_to_peer also requires explicit input of the context address. You can check the address by calling qmi.show_network_contexts(). Then give the whole address as second parameter in the call with peer_address=<the_address>. If the connecting now went without an exception, everything should be now OK. You can confirm this by calling again qmi.show_network_contexts()and see that the ‘connected’ column has now changed to ‘yes’. Then proceed to get the instrument and a sample.

NOTE 2: peer_address=”127.0.0.1:40001” also works as the ‘localhost’ address changes into the IPv4 address in the background. This exercise demonstrated how the second Python program is able to access the NoisySineGenerator instance proxy that exists within the first Python program (and QMI context within it). To do this, the QMI context of the second program connects to the “instr_server” context via TCP. Behind the scenes, the two contexts exchange messages through this connection to arrange for the method get_sample() to be called in the real instrument instance through the proxy, and the answer to be sent back to the calling proxy in the second program.

Key Points
  • Instruments to be accessed remotely should be defined in `qmi.conf
  • Connect to another QMI context using qmi.context().connect_to_peer("<context_name>", peer_address="<ho.st.i.p:port>")
  • Obtain remote instrument control with qmi.get_instrument("<context_name>.<instrument_name>")

Content from Create a task and a 'service'


Last updated on 2025-08-22 | Edit this page

Overview

Questions

  • What are tasks and what can they do?
  • Can I run a task as a (background) ‘service’ process?

Objectives

  • Learn how to create tasks with (customized) task runners
  • Learn how to make a background ‘service’ process with qmi_proc

Setting up a task and a “service”


Next, we want to demonstrate QMI tasks and how they can be used in setting up services, i.e. tasks running as background processes, on your PC. This needs now somewhat more complex configuration of the qmi.conf, but nothing scary, I promise.

Configuration file

The new configuration will have an extension for the background process:

{
    # Log level for messages to the console.
    "logging": {
        "console_loglevel": "INFO"
    },
    "contexts": {
        # Testing remote instrument access.
        "instr_server": {
            "host": "127.0.0.1",
            "tcp_server_port": 40001
        },
        # Testing process management.
        "proc_demo": {
            "host": "127.0.0.1",
            "tcp_server_port": 40002,
            "enabled": true,
            "program_module": "task_demo "
        }
    }
}

The ‘”enabled”: true’ parameter makes it possible to start the context via QMI process management, and ‘”program_module”: “task_demo”’ line tells the QMI to start a module named ‘task_demo.py’ for this process. We’ll create it in a qubit, but first let’s define a task we want to make the service for.

Demo task

To demonstrate a custom task, we need to create one. Make a new Python module inside the module path for your project. If you don’t have a module path yet, just create a file demo_task.py in the current directory:

PYTHON

from dataclasses import dataclass
import logging
import qmi
from qmi.core.rpc import rpc_method
from qmi.core.task import QMI_Task, QMI_TaskRunner


# Global variable holding the logger for this module.
_logger = logging.getLogger(__name__)


@dataclass
class DemoLoopTaskSettings:
    sample: float
    amplitude: float


class CustomRpcControlTaskRunner(QMI_TaskRunner):
    @rpc_method
    def set_amplitude(self, amplitude: float):
        settings = self.get_settings()
        settings.amplitude = amplitude
        self.set_settings(settings)


class DemoRpcControlTask(QMI_Task):
    def __init__(self, task_runner, name):
        super().__init__(task_runner, name)
        self.settings = DemoLoopTaskSettings(amplitude=100.0, sample=None)

    def run(self):
        _logger.info("starting the background task")
        nsg = qmi.get_instrument("proc_demo.nsg")
        while not self.stop_requested():
            self.update_settings()
            nsg.set_amplitude(self.settings.amplitude)
            self.sleep(0.01)

        _logger.info("stopping the background task")

Note that we define class DemoRpcControlTask with one special method named run(). This method contains the code that makes up the background task. In this simple example, the task simply loops once per second, reading settings from the sine generator and adjusting its amplitude. The task uses the function qmi.core.task.QMI_Task.sleep() to sleep instead of time.sleep(). The advantage of this is that it stops waiting immediately when it is necessary to stop the task.

Task runner script

Now we still miss the program starting up and running the task. Make the task_demo.py file with the following contents:

PYTHON

import logging
import time
import qmi
from qmi.utils.context_managers import start_stop
from qmi.instruments.dummy.noisy_sine_generator import NoisySineGenerator
from demo_task import DemoRpcControlTask, CustomRpcControlTaskRunner

# Global variable holding the logger for this module.
_logger = logging.getLogger(__name__)


def main():
    with start_stop(qmi, "proc_demo", config_file="./qmi.conf"):
        ctx = qmi.context()
        with qmi.make_instrument("nsg", NoisySineGenerator) as nsg:
            with qmi.make_task("demo_task", DemoRpcControlTask, task_runner=CustomRpcControlTaskRunner) as task:
                _logger.info("the task has been started")
                while not ctx.shutdown_requested():
                    sample = nsg.get_sample()
                    amplitude = nsg.get_amplitude()
                    print(" " * int(40.0 + 0.25 * sample) + "*")
                    if abs(sample) > 10:
                        task.set_amplitude(amplitude * 0.9)
                    else:
                        task.set_amplitude(amplitude * 1.1)

                    time.sleep(0.01)

            _logger.info("the task has been stopped")


if __name__ == "__main__":
    main()

This program now takes care of creating the instrument nsg and followingly starting up the task. The script also loops about once per second, and at each iteration prints out the latest sample and amplitude values, and controls the DemoRpcControlTask’s amplitude to keep the sample value at around 10. In practice, we are now trying to suppress the sine wave as much as possible by continuously controlling its amplitude.

Running the task as a service

We can now start the service using qmi_proc program. qmi_proc is a command-line executable created when installing QMI, see also documentation about managing background processes. It can be used to start, stop and inquire status of services. To start the “proc_demo” service, type the following (the “–config ./qmi.conf” is not necessary if the qmi.conf is in the default path).

qmi_proc start proc_demo --config ./qmi.conf

Leave the program now to run a few seconds and then stop it:

qmi_proc stop proc_demo --config ./qmi.conf

The use of the qmi_proc creates extra output files for services. One was now created also for “proc_demo” service. You can find it in the default location with name “proc_demo__

There are plenty of other things going on on this example, like the use of a custom QMI_TaskRunner with an RPC method added into the runner. We also make use of file-specific loggers for logging. For more information about QMI tasks see the tutorial, and about logging the Design Overview in documentation. There are also other examples of tasks in the examples folder of the QMI repository.

Further, the amplitude control through the task settings actually utilizes the QMI’s signalling feature. For more details on this, you can read into signalling and look at API of qmi.core.task in the documentation.

Key Points
  • Instruments to be accessed remotely should be defined in `qmi.conf
  • Connect to another QMI context using qmi.context().connect_to_peer("<context_name>", peer_address="<ho.st.i.p:port>")
  • Obtain remote instrument control with qmi.get_instrument("<context_name>.<instrument_name>")

Content from Open-source vs internal code


Last updated on 2025-08-25 | Edit this page

Overview

Questions

  • What is the difference between different QMI codebases?
  • How is the development done for QMI software?
  • Can I make requests and|or contribute?

Objectives

  • Know what is the difference between the open-source and internal QMI
  • Know how to make requests and how to contribute to the development

QMI open-source vs internal code


QMI has its main development as an open-source software (OSS). But it has also a close-source version (CSS) for development mainly due to licensing reasons as QMI uses also some manufacturer software and libraries. Also for some projects it might be preferable to keep certain aspects out of plain sight during their course. The software can be found in - OSS: https://github.com/QuTech-Delft/QMI (public) - CSS: https://gitlab.tudelft.nl/qmi/qmi (private, you need to be invited to access) repositories.

Code base and development


By browsing the repositories from the links above (if you have access for both), you can get familiar with the code base of both projects. You will see things on the site like “Issues” or “Pull requests” (Github) | “Merge requests” (Gitlab) and “Issue Board” (Gitlab) among others. The development is done normally by tackling the “Issues”. We can call them as “Issue tickets” or just “tickets” as well.

Figure 2: An issue list in Github.
Figure 2: An issue list in Github.

The use of such issue list is that we want to have more traceable and logical system for the development. If a developer or user notices a bug or another issue with the code, or would like to have new features or functionalities, it is best to describe it (as well as possible) in an issue ticket, instead of immediately starting to bash through some changes in the code and try to push that through in the code base. In any case where there is more than one person involved in the code base (and usually even for the lone wolf developer) this is a good idea to prevent too eager contributors messing up the code.

Creating a new issue ticket

To avoid the “writer’s block” when creating a new ticket, templates are available for different ticket types. The templates have a structure ready and a description what should be written in different parts of the ticket. The templates do not need to be written 100% full if some parts are not applicable, though. Use your judgement also. In general applies: The more detailed request with clear description of the desired outcome, perhaps even with a little bit of (meta)code, the easier the ticket is to evaluate and execute, and eventual pull|merge request reviewed. Another common pitfall to avoid is to make too large tickets. If the new feature request starts to get really complex, perhaps it is better to think how it can be split into two or more smaller tickets, with clear reachable goals for each ticket, which together then fulfill the request. This might also be done during the evaluation of the issue, see below the description of ticket progress in QMI.

Tracking issue progress on a board

A ticket progress can be followed on a issue board. A board consists of at least “new” or “open” column and “done” or “closed” columns. In between there can be several other columns to keep track of the ticket progress, like “refinement”, “in progress”, “for review” and others. Below is an example of QMI OSS (private) issue development board.

Figure 3: QMI issue development board.
Figure 3: QMI issue development board.

The general progress order is from left to right, where first new issues are made in the first column. If the ticket is written clear and concise enough, it can be moved to be evaluated by the team in “To poker” column. Here, the ticket is “pokered” to have an effort score and can then be then moved to “Backlog” column to wait someone to start working on the issue. Or directly to “In progress” if the issue is going to be worked on straight away. In the code base a new branch is opened (usually from main branch for the repository) to work on the ticket. When the ticket is ready, a pull|merge request is made for the request and the ticket is moved into the “In review” column. Another developer must review the ticket and accept it before the new code in the new branch can be merged. After the code is merged back to its origin, the issue can be moved into the “Pending release” column. When ‘enough’ (term is relative here) issues are in this column, a new release of the code can be made and finally the ticket is moved into “Done”.

We can also move tickets back to left, e.g. if the issue description is not proper for “pokering”, we can send it back to “New issues” to indicate better description is needed. Sometimes, the issue is so large that tackling it in one ticket becomes way too cumbersome. Then splitting it into several smaller new issues is necessary. Quite often also tickets “In review” do not get accepted with extra work needed on the ticket, often the ticket being moved back to “In progress”.

A specialty if the board in Figure 3 is the “Epics” column. This column has issue tickets that are actually descriptions of a large feature request or larger changes in the code base. The Epic ticket describes what is the total goal and how to reach it taking smaller steps, i.o.w. by executing several smaller issue tickets. The aforementioned splitting of a large ticket during evaluation could also result into creation of such Epic issue so that the overall progress, which could span even over multiple releases, can be tracked better.

Now you will be showcased how to make a ticket on a board and how to select a template for it.

Contributing


The users can also pick up their own tickets and develop code in their own branches, and offer them for merging, too! Such contributions are more than welcome as there’s not too many dedicated SW engineers for development. For QMI, we do aim to keep a certain coding style and have some quality requirements for any contribution. QMI coding standard is explained here and a simple guide on how to proceed is here. Note that just delivering code for the bug fix or requested feature is not enough: the code must include also unit-tests and pass the CI-pipelines, fulfilling our acceptance criteria. Only then the pull|merge request can be approved and merged. Other valuable points for good coding practices were mentioned in the IMSEP course.

Key Points
  • In Github we have open-source QMI and in Gitlab the internal closed-source QMI
  • Users can make requests via opening new issues a.k.a. tickets in the repository
  • You can and are welcome to contribute too! Just follow the guidelines

Content from Good coding practises: MyPy


Last updated on 2025-08-22 | Edit this page

Overview

Questions

  • What is MyPy and why is it used?
  • How do I code such that MyPy pipelines pass?

Objectives

  • Learn to code taking into account MyPy typing requirements

MyPy


In the IMSEP course some clean code tools, which are also employed for QMI, are explained, like PyLint and Coverage. Another common tool we employ, not mentioned in the course, is MyPy. One of the factors that make Python easier to start with coding, compared to e.g. C/C++ code is that the type of a variable does not need to be declared. Like, when we declare a variable that should be a floating point number in C, the variable can really be assigned only with a floating point number and nothing else. Trying to give it an integer value will result in an error. Python doesn’t care and lets you do it. While it gives flexibility (floating points and integers are both numbers, right?), it also poses danger. What if the variable accidentally is a string “1.5”, if for example read from a file? This certainly will raise an error when trying to do mathematical operations with it.

Using typing together with an advanced IDE, like PyCharm, can notice this kind issues and warn you in the editor: If the return type of the value read from a file is typed as a string while your variable you read it into is typed as float, the editor will notice this and warn you with a squiggly line under it or some other way. But, it doesn’t force you to fix it. While checking the code with MyPy will raise an error for this line of code telling you to fix the problem. The QMI’s CI pipeline for the code pushed into the repository won’t pass before the issue is fixed – and also does not let the code to be allowed to be merged back to origin.

This whole MyPy check might feel as an unnecessary nuisance at start, but when the code base gets larger and also the code includes all colours and flavours of variable types, even some really exotic ones, the typing can save you from making hard-to-trace bugs already before committing the code back to the repository. To be really honest, probably every developer does see the advantage of MyPy and at the same time be annoyed by it. MyPy also evolves over time and it often happens that a typing construction made years ago, which passed any MyPy check without issues, suddenly stops passing after a new MyPy version release. These can sometimes lead into lengthy problem-solving issue tickets, but just keep thinking it is for the greater good…

Modern typing with built-in classes is simple and out-of-the-box:

PYTHON

fraction: float = 0.92
eggs: int = 5
colour: str = “blue”
guests: list[str] = [“Charly”, “Douwe”, “Ebat”]

Functions should be typed too:

PYTHON

def summer_fun(a: int | float, b: int | float) -> int | float:
    sum = a + b
    return sum

The function summer_fun expects two inputs that should be either a floating point or integer numbers. The return value is the sum of the two and can also be either floating point or integer number. If you have somewhere else in your code:

PYTHON

a = input(“Give number a: “)
b = input(“Give number b: “)
sum = summer_fun(a, b)

The user might get surprised that after giving number a as 1 and number b as 2, the sum is 12! How did that happen? Well, the reason is that input returns the given answer always as a string. And summing two strings, like “a” and “b” is simply “ab”. So, the code works but it does the wrong thing. But, as we have typed the summer_fun, inspecting the code with an IDE that also checks typing would give warnings here. And running a MyPy check on this could would give an error telling that the input values “a” and “b” are of type str (strings), while it expected type int or float for them. So the error could have passed unnoticed if no type checking was done. The solution would be to cast the input values either as an int (will crash if a decimal value is inputted!) or float before or at the function call to summer_fun.

Let’s do some exercises with typing.

Install MyPy:

pip install mypy

Create a program with following code:

PYTHON

def summer_fun(a: int, b: int) -> int:
    sum = a + b
    return sum

debts: str = -100.00
alice_has = input("How much money does Alice have?\n > ")
bob_has = input("How much money does Bob have?\n > ")
alice_and_bob_have = summer_func(alice_has, bob_has)
left_sum: int = debts + alice_and_bob_have
print(f"Alice and Bob have together {alice_and_bob_have:0.2f} of money.")
print(f"They need to pay {-debts:0.2f} money.")
if left_sum > 0:
    print(f"Alice and Bob have enough money and are left with {left_sum:0.2f} money.")
elif left_sum < 0:
    print(f"Alice and Bob do not have enough money and need {-left_sum:0.2f} more.")
else:
    print(f"Alice and Bob have exactly enough money to pay their debts.")

Run MyPy check on the program

mypy alice_and_bob_in_debts.py

OUTPUT

mypy_exercise.py:5: error: Incompatible types in assignment (expression has type "float", variable has type "str")  [assignment]
mypy_exercise.py:8: error: Argument 1 to "summer_fun" has incompatible type "str"; expected "int"  [arg-type]
mypy_exercise.py:8: error: Argument 2 to "summer_fun" has incompatible type "str"; expected "int"  [arg-type]
mypy_exercise.py:9: error: Unsupported operand types for + ("str" and "int")  [operator]
mypy_exercise.py:11: error: Unsupported operand type for unary - ("str")  [operator]
Found 5 errors in 1 file (checked 1 source file)

If you made the program with a nice IDE like PyCharm, probably some squiggly lines also appear on the same lines where MyPy raised the errors, indicating where the errors are.

Exercise 1: Now, with the help of MyPy and/or IDE, can you identify the issues in the code?

Exercise 2: Now let’s assume you try to correct the issues by changing the annotation of debts to float, and casting the inputs to summer_fun as integers using alice_has = int(alice_has) and bob_has = int(bob_has). You see the same amount of errors! Why?

So we realize that the casting went wrong, and that there is an issue with typing assignment due to some values typed as float and others as int.

Exercise 3: OK, let’s fix this further by doing the casting directly in the function call, without reassigning the alice_has and bob_has. We also cast the debts in the summation as an int. What does MyPy say now? Will the code work?

So, the code works, but only with whole numbers and not with decimals. To fix this we can use the fact that Python can cast directly to float both integer and decimal input strings. Now, cast the alice_has and bob_has into floats. We should also allow the summer_fun input parameters be floats and return a float as well. Then as last step the left_sum should also be typed as a float and no casting of debts should be done.

Key Points
  • MyPy is a tool to check typing in code
  • Using typing can reveal coding errors like mis- or double use of same variable leading to exceptions in code execution
  • A good IDE can help to find typing issues
  • Mypy does not check typing for string formatting

Content from Releasing and versioning of QMI code


Last updated on 2025-08-22 | Edit this page

Overview

Questions

  • How is QMI code released?
  • What are the differences between different types of ways to obtain QMI?

Objectives

  • Understanding of ways and differences QMI code is released and can be obtained

Releasing, released packaged and stable branches/tags


Finally, a little bit about the releasing of QMI code. Every now and then, after some amount of issues have been approved and merged into main, a new stable branch, tag, release and a package are made. Why so many and what’s the difference? Well, let us explain.

A stable branch is created for each release new version-revision, like 0.49.0 (branch name stable-0-49). If for some reason, later on some user, who needs to using this release version, needs to fix a bug but not to upgrade version, the fix can be done based on the stable branch. Now, instead of branching off from the main branch, we branch off from the stable branch, and also merge back to it. A new tag, release, and package of the fix can be made with upgrading the version’s patch number by one. In this case, the new versioning is 0.49.1. If the fix is relevant also for the main branch, it can be “cherry-picked” also there, but it might not be the case.

A tag is usually created together with a stable branch and possible patches to it. Our naming convention for tags is v.., like v0.49.0 if using the same example as above. The tag is a simple snapshot of the branch it is made in at that point in time. It is easy to check out to return to that point in time to check how the code was. It can also be used to revert the code to that version. But in comparison with stable branch, it cannot be used for branching off or merging to it, and no changes can be committed.

A release releases the source code as a Zip and a Gzipped Tar files. These can be downloaded and used to install the package for example with Pip locally. These are especially handy if no installable package is available.

A package is an installable wheel or equal type of file, which can be used to install the software. QMI creates a new package every time a new tag is made, builds a wheel for it and uploads it to QMI’s Pypi page. From there QMI can be installed with simply using the pip install qmi command, or, if we want to install e.g. specifically version 0.49.1, pip install qmi==0.49.1.

Key Points
  • While main version-revision development happens through the main branch, also version-revision-specific development can be done through stable branches
  • User can also use tags as references to code at certain point of time
  • The usual way to install QMI is using Pip and a Pypi package of QMI. But, also releases can be downloaded and used for the installation.