Baptiste Fontaine’s Blog  (back to the website)

Typing Tuples in Python

Python added support for type hints in 3.5. These are not typing as you may be used to in other languages since they have no effect on the compilation to bytecode nor at runtime, but rather hints for the (tools of the) developer.

def print_int(n: int):
    print(n)

print_int(1)
print_int("foo")

The code above doesn’t fail when you call print_int("foo") even though n is “typed” as an int. This is because this n: int is just a hint.

While you can check for type issues by running mypy by hand, type hints become really powerful when your editor/IDE supports them.

Types for collections can specify the inner types: a list (List) that contains strings (str) would be List[str].

from typing import List

stuff: list = []  # a list of anything (equivalent to List[Any])
stuff.append(1)
stuff.append("foo")

offsets: List[int] = []  # a list of ints
offsets.append(1)
offsets.append("foo")

In the snippet above, the last line is highlighted as an error in any good IDE, and mypy would complain about it.

Other container types exist as well, and they can be nested:

from typing import List, Dict, Iterable

# a dictionary where keys are str and values are List[str], i.e. lists of strings
friends: Dict[str, List[str]] = {}
friends["Alice"] = ["Sam", "Maria"]

# a function that takes an iterable of ints. It can be a list, a tuple, a generator, a set, etc
def average(s: Iterable[int]):
    total = 0
    count = 0
    for element in s:
        total += element
        count += 1

    return total / count

Given List[x], Collection[x], Sequence[x] and other Set[x], one would expect Tuple[x] to be a hint for a tuple that contains x. Well… no.

This is confusing at first, but Tuple[str] types a tuple of a single element of type str. To add more elements, you need to type them as well: a pair of ints would be Tuple[int, int], while a triplet of a string, a float and a boolean would be Tuple[str, int, bool].

While tuples can be used as sequences (e.g. for immutable/hashable equivalents to lists), I’d argue that their primary use is for fixed-length representations, such as pairs of results:

def match_object(data: bytes):
    # example code
    distances = [0.99, 0.97, 0.96]
    indices = [432, 12, 3923]
    return distances, indices

In this snippet, match_object returns a tuple of a list of floats and a list of integers (aka Tuple[List[float], List[int]]).

If you still want to type arbitrary-length homogeneous tuples, there’s a syntax for that: Tuple[int, ...] types a tuple of any length, including 0, that contains only int elements (and yes, ... is valid in Python).

For this and other interrogations (how to type a generator?), Mypy has a useful type hints cheat sheet.

TL;DR: if you know the size of the tuple, use Tuple[x], Tuple[x, x], Tuple[x, x, x], etc. If you don’t, use Tuple[x, ...], but all elements must be of type x.

Fix bin/magento taking all the RAM

While working with Magento 2.3.6 on Bixoto I hit a weird issue: the bin/magento command-line tool was always eating all the RAM, even with a simple command:

$ ./bin/magento --help
PHP Fatal error:  Allowed memory size of 2147483648 bytes exhausted (tried to allocate 262144 bytes) in /home/baptiste/.../vendor/magento/module-store/Model/Config/Placeholder.php on line 146
Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.

The issue, as weird as it sounds, is an empty configuration value that causes Magento to end up in an infinite loop.

When I installed Magento on my local machine, I deactivated HTTPS by setting web/secure/base_url to NULL in the table core_config_data. This alone is the cause of the issue.

Check in MySQL:

SELECT * FROM core_config_data WHERE path = 'web/secure/base_url' LIMIT 1;

If this shows a line with a NULL value, either delete it or replace it with some non-null value:

UPDATE core_config_data SET value='http://...' WHERE path = 'web/secure/base_url' limit 1;

This has been reported to Magento but was closed because “it’s not a bug”. I don’t think falling in an infinite loop on --help because some config value is NULL should really be a normal behavior, but at least now you know how to solve it.

Fix Virtualbox installation for Docker on macOS

While following a tutorial to install Virtualbox in order to have docker working on macOS, I hit an issue where the docker-machine create command fails with an error that looks like this:

VBoxManage: error: Failed to create the host-only adapter
VBoxManage: error: VBoxNetAdpCtl: Error while adding new interface: failed to open /dev/vboxnetctl: No such file or directory
VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component HostNetworkInterfaceWrap, interface IHostNetworkInterface
VBoxManage: error: Context: "RTEXITCODE handleCreate(HandlerArg *)" at line 95 of file VBoxManageHostonly.cpp

If you search on the Web, everybody says you have to open the Security & Privacy settings window and allow the Oracle kernel extensions to run. But I didn’t have it. I tried uninstalling Virtualbox, re-installing through the official website, reboot, uninstall, re-install with brew cask but I always had the issue. Some people reported having a failed Virtualbox installation but mine seemed ok.

I tried the spctl solution but it didn’t change anything.

In the end, I tried this StackOverflow answer:

sudo "/Library/Application Support/VirtualBox/LaunchDaemons/VirtualBoxStartup.sh" restart

It failed, but it told me to check the Security & Privacy setting window. I did, and I had the button everyone was talking about. I enabled the kernel extension, rebooted, and it worked.

Hope this can save some time to anyone having the same issue!