Python - Tips & snippets


pip3 : how to make an "offline install" ?

It is pretty common that servers aren't directly connected to the Internet, which makes it impractical to download + install packages with tools like pip3 or apt. The workaround is then to make an "offline install" :
  1. from a machine that has internet access, download the required packages and their dependencies :
    pip3 download ansible
    Collecting ansible
      Downloading (14.2MB)
        100% |████████████████████████████████| 14.3MB 94kB/s
      Saved ./ansible-2.9.11.tar.gz
    Collecting PyYAML (from ansible)
      Downloading (269kB)
        100% |████████████████████████████████| 276kB 1.1MB/s
      Saved ./PyYAML-5.3.1.tar.gz
    Collecting cryptography (from ansible)
      Downloading (2.7MB)
        100% |████████████████████████████████| 2.7MB 368kB/s
      Saved ./cryptography-3.0-cp35-abi3-manylinux1_x86_64.whl
    Collecting jinja2 (from ansible)
      Downloading (125kB)
        100% |████████████████████████████████| 133kB 2.2MB/s
      Saved ./Jinja2-2.11.2-py2.py3-none-any.whl
    Collecting six>=1.4.1 (from cryptography->ansible)
      Saved ./six-1.15.0-py2.py3-none-any.whl
    Collecting cffi!=1.11.3,>=1.8 (from cryptography->ansible)
      Downloading (401kB)
        100% |████████████████████████████████| 409kB 1.1MB/s
      Saved ./cffi-1.14.1-cp37-cp37m-manylinux1_x86_64.whl
    Collecting MarkupSafe>=0.23 (from jinja2->ansible)
      Using cached
      Saved ./MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl
    Collecting pycparser (from cffi!=1.11.3,>=1.8->cryptography->ansible)
      Using cached
      Saved ./pycparser-2.20-py2.py3-none-any.whl
    Successfully downloaded ansible PyYAML cryptography jinja2 six cffi MarkupSafe pycparser
  2. send the downloaded files to the destination machine
  3. install packages locally :
    pip3 install path/to/downloadedPackage
    • PyYAML is a dependency of ansible and it looks like pip3 install ./ansible-2.9.11.tar.gz is not able to install PyYAML from the local package (tries to download it instead). In such case, install dependencies first, then retry installing the main package.
    • Looks like the downloaded packages (architecture, version, ... ?) match the machine they are downloaded from, but may not fit the destination machine and fail to install (to be investigated).

pip3 : error: invalid command 'bdist_wheel'

Situation :

Full error message :
  Failed building wheel for ansible
  Running clean for ansible
  Running bdist_wheel for pycparser ... error
  Complete output from command /home/kevin/myGreatProject/myVirtualEnvironment/bin/python3 -u -c "import setuptools, tokenize;__file__='/tmp/pip-install-bz2ssx4w/pycparser/';f=getattr(tokenize, 'open', open)(__file__);'\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" bdist_wheel -d /tmp/pip-wheel-0z6zygi4 --python-tag cp37:

  error: invalid command 'bdist_wheel'

I got this error while installing Ansible but it can occur during the installation of any package since something is missing locally .

Solution :

  1. pip3 install wheel --upgrade
  2. retry the command you were running in the first place

pip3 : how to list all available versions of a package and install a specific version ?

When playing with packages and versions, it may be safe to work inside a virtual environment.

List all available version of a package (source) :

Looks like there is no tool or command flag to do so, and the only option is to ask the installation of a non-existent version to read the expected information from the error message :
pip3 install ansible==
Collecting ansible==
  Could not find a version that satisfies the requirement ansible== (from versions: 1.0, 1.1, 1.2, 1.2.1, 1.2.2, 1.2.3, 1.3.0, 1.3.1, 1.3.2, 1.3.3, 1.3.4, 1.4, 1.4.1, 1.4.2, 1.4.3, 1.4.4, 1.4.5, 1.5, 1.5.1, 1.5.2, 1.5.3, 1.5.4, 1.5.5, 1.6, 1.6.1, 1.6.2, 1.6.3, 1.6.4, 1.6.5, 1.6.6, 1.6.7, 1.6.8, 1.6.9, 1.6.10, 1.7, 1.7.1, 1.7.2, 1.8, 1.8.1, 1.8.2, 1.8.3, 1.8.4,, 1.9.1, 1.9.2, 1.9.3, 1.9.4, 1.9.5, 1.9.6,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2.5.0a1, 2.5.0b1, 2.5.0b2, 2.5.0rc1, 2.5.0rc2, 2.5.0rc3, 2.5.0, 2.5.1, 2.5.2, 2.5.3, 2.5.4, 2.5.5, 2.5.6, 2.5.7, 2.5.8, 2.5.9, 2.5.10, 2.5.11, 2.5.12, 2.5.13, 2.5.14, 2.5.15, 2.6.0a1, 2.6.0a2, 2.6.0rc1, 2.6.0rc2, 2.6.0rc3, 2.6.0rc4, 2.6.0rc5, 2.6.0, 2.6.1, 2.6.2, 2.6.3, 2.6.4, 2.6.5, 2.6.6, 2.6.7, 2.6.8, 2.6.9, 2.6.10, 2.6.11, 2.6.12, 2.6.13, 2.6.14, 2.6.15, 2.6.16, 2.6.17, 2.6.18, 2.6.19, 2.7.0.dev0, 2.7.0a1, 2.7.0b1, 2.7.0rc1, 2.7.0rc2, 2.7.0rc3, 2.7.0rc4, 2.7.0, 2.7.1, 2.7.2, 2.7.3, 2.7.4, 2.7.5, 2.7.6, 2.7.7, 2.7.8, 2.7.9, 2.7.10, 2.7.11, 2.7.12, 2.7.13, 2.8.0a1, 2.8.0b1, 2.8.0rc1, 2.8.0rc2, 2.8.0rc3, 2.8.0, 2.8.1, 2.8.2, 2.8.3, 2.8.4, 2.8.5, 2.9.0b1, 2.9.0rc1, 2.9.0rc2, 2.9.0rc3)
No matching distribution found for ansible==
Improved version :
pip3 install ansible== 2>&1 | tr '), ' '\n' | grep -E '^[0-9]' | sort -nr | less

Install a specific version :

From above :
pip3 install ansible==2.7.9

pip3 ends on error: command 'x86_64-linux-gnu-gcc' failed with exit status 1

Situation :

While trying to setup Molecule, a pip3 install -U [packages] command ends on :
building 'psutil._psutil_linux' extension
creating build/temp.linux-x86_64-3.7
creating build/temp.linux-x86_64-3.7/psutil
x86_64-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -DPSUTIL_POSIX=1 -DPSUTIL_VERSION=563 -DPSUTIL_LINUX=1 -I/home/bob/virtualenvs/projectName/include -I/usr/include/python3.7m -c psutil/_psutil_common.c -o build/temp.linux-x86_64-3.7/psutil/_psutil_common.o
psutil/_psutil_common.c:9:10: fatal error: Python.h: No such file or directory	the line above the error 
 #include <Python.h>
compilation terminated.
error: command 'x86_64-linux-gnu-gcc' failed with exit status 1			the error

Details :

As said here : Sometimes it's the line above this error you should look at. It should tell you what package is missing.

Solution :

apt install python3-dev

Check :

dpkg -S Python.h
libpython3.7-dev:amd64: /usr/include/python3.7m/Python.h


Python's Virtual environments, virtualenv, venv

What are Virtual environments ? Why do we need them ? (source)

  • if you work on a single project : you don't need Virtual environments
  • if you work on projectA and projectB, which respectively require lib1 + lib2 and lib3 + lib4 : you don't need Virtual environments
  • if you work on projectA and projectB, which respectively require someGreatLib (version 12.9) and someGreatLib (version 42.0) : Virtual environments will help you have both versions on your computer.

Usage (source) :

virtualenv is the Python 2 tool to interact with virtual environments and won't be discussed here. I'll focus on Python 3 tools and methods only.

The Python 3 tool to manage virtual environments is venv, which —depending on your distro / version :

  • is built in the standard library
  • can be installed with : apt install python3-venv

Create a virtual environment :

The virtual environment itself :
  • is a directory (full of subdirs, libs and stuff )
  • is named after the "virtual environment name" you will supply
  • is created in the current directory
  • is a developer tool and need NOT be created by root
  1. cd my/development/directory
  2. create the virtual environment :
    virtualEnvName='myVirtualEnvironmentName'; python3 -m venv "$virtualEnvName"
  3. activate it :
    source "$virtualEnvName/bin/activate"
    The shell prompt should now be preceded by :
    showing the virtual environment is enabled.

Exit a virtual environment :

From within the virtual environment directory or not, as long as the virtual environment name is visible in the shell prompt :
(myVirtualEnvironmentName) disappears.

Resume / reload a virtual environment :

Simply :
source bin/activate
based on the virtual environment you want to resume. To do so :
  • cd into the corresponding virtual environment directory first
  • or prepend bin/activate with the corresponding path

Error after upgrading pip : ImportError: cannot import name 'main'

Situation :

I don't have the exact sequence of events and commands that led me here, but what I read while fixing this emphasize that PEBKAC :
  1. while actually trying to use youtube-dl, it displayed my current version was out-of-date and I should upgrade (which I did)
  2. the upgrade process suggested to upgrade pip too, and even offered the command line to do so. I copy-pasted it and executed it ()
  3. after that, every youtube-dl command failed on :
    Traceback (most recent call last):
      File "/usr/local/bin/youtube-dl", line 6, in <module>
        from youtube_dl import main
    ImportError: cannot import name 'main'

Details :

Solution :

This GitHub issue details the reasons of this error (the PEBKAC part, notably) and hints to debug / fix.

Unless explicitly specified, all commands below were run as a non-root user.
  1. First, identify the full absolute path of the executable script that you are running :
    which youtube-dl
  2. Once you have identified the script, make sure you can reproduce the problem using the absolute path to that script :
    Traceback (most recent call last):
      File "/usr/local/bin/youtube-dl", line 6, in <module>
        from youtube_dl import main
    ImportError: cannot import name 'main'
  3. work out which version of Python the script is using to run pip. You'll often be able to get that from the shebang line of the script :
    head /usr/local/bin/youtube-dl
  4. Once you know which Python is running pip, you can run python -m pip to invoke pip :
    python -m pip
    (returns the 'usage' page, but no error)
    python3 -m pip
      File "/usr/share/python-wheels/pkg_resources-0.0.0-py2.py3-none-any.whl/pkg_resources/", line 639, in __init__
      File "/usr/share/python-wheels/pkg_resources-0.0.0-py2.py3-none-any.whl/pkg_resources/", line 695, in add_entry
      File "/usr/share/python-wheels/pkg_resources-0.0.0-py2.py3-none-any.whl/pkg_resources/", line 2012, in find_on_path
    PermissionError: [Errno 13] Permission denied: '/usr/local/lib/python3.5/dist-packages/youtube_dl-2019.3.18.dist-info'
    The last line is quite surprising and also recalls something seen minutes ago. The permissions were actually kinda weird :
  5. as root :
    ll /usr/local/lib/python3.5/dist-packages
    drwx--S--- 6 root staff 4.0K Mar 19 08:52 youtube_dl
    drwx--S--- 2 root staff 4.0K Mar 19 08:52 youtube_dl-2019.3.18.dist-info
  6. as root again :
    chmod -R a+rX /usr/local/lib/python3.5/dist-packages/
  7. then, as non-root :
    (the 'usage' page )

This is highly context-specific and not exactly a "clean" solution, but maybe this will give you hints to find the root cause on your side... If anything, this issue,at least, led me to some best practices :

Best practices (source) :

First, some general advice. It is assumed that anyone encountering issues will have failed to follow some part of this advice. Listing these items here is not intended to imply that "it's your fault and we won't help", but rather to help users to identify what went wrong, so that they can work out what to do next more easily.

  1. Only ever use your system package manager to upgrade the system pip. The system installed pip is owned by the distribution, and if you don't use distribution-supplied tools to manage it, you will hit problems. Yes, we know pip says "you should upgrade with pip install -U pip" - that's true in a pip-managed installation, ideally distributions should patch this message to give appropriate instructions in the system pip, but they don't. We're working with them on this, but it's not going to happen soon (remember, we're looking at cases where people are upgrading old versions of pip here, so patches to new versions won't help).
  2. Never use sudo with pip. This follows on from the first point. If you think you need to use sudo, you're probably trying to modify a distribution-owned file. See point 1.
  3. Prefer to use --user. By doing this, you only ever install packages in your personal directories, and so you avoid interfering with the system copy of pip. But there are PATH issues you need to be aware of here. We'll cover these later. Put simply, it's possible to follow this advice, and still hit problems, because you're not actually running the wrapper you installed as --user.

pip ends on error TypeError: unsupported operand type(s) for -=: 'Retry' and 'int'

Situation :

While trying to install mps-youtube (sudo pip3 install mps-youtube), I get the error :
MANY lines describing the exceptions
TypeError: unsupported operand type(s) for -=: 'Retry' and 'int'

Details :

This is because the installed version of pip (probably from the Debian packages) is too old.

Solution :

  1. apt remove python-pip python3-pip
  2. proxyString="Kevin:p4ssw0rd@proxy:3128"; export http_proxy="http://$proxyString"; export https_proxy="https://$proxyString"
  3. cd /tmp && wget
  4. python
    Collecting pip
      Downloading pip-9.0.1-py2.py3-none-any.whl (1.3MB)
        100% |████████████████████████████████| 1.3MB 781kB/s
    Installing collected packages: pip
    Successfully installed pip-9.0.1
  5. python3
    Collecting pip
      Using cached pip-9.0.1-py2.py3-none-any.whl
    Collecting wheel
      Downloading wheel-0.30.0-py2.py3-none-any.whl (49kB)
        100% |████████████████████████████████| 51kB 718kB/s
    Installing collected packages: pip, wheel
    Successfully installed pip-9.0.1 wheel-0.30.0
  6. You can now retry installing :
    /usr/local/bin/pip3 install mps-youtube
    Collecting mps-youtube
      Downloading mps_youtube- (74kB)
        100% |████████████████████████████████| 81kB 974kB/s
    Requirement already satisfied: pafy!=0.4.0,!=0.4.1,!=0.4.2,>=0.3.82 in /usr/lib/python3/dist-packages (from mps-youtube)
    Installing collected packages: mps-youtube
    Successfully installed mps-youtube-

How to use dynamic variables ?

A code snippet is worth 1000 words :
#!/usr/bin/env python3

class Person(object):

    def __init__(self, firstName, lastName, age):
        self.firstName = firstName
        self.lastName = lastName
        self.age = age

    def getItem(self, itemName):
        return getattr(self, itemName)

user = Person('John', 'Smith', 42)

for item in [ 'firstName', 'lastName', 'age' ]:
will display :

The Python Challenge

The Python Challenge is a series of puzzles aiming at learning / practicing / improving Python development skills.
In notes below :

Level 0 :

import math

Next level :

Level 1 :

inTable = 'KOE'
outTable = 'MQG'
message = 'KOE'
print(message.translate({ord(x): y for (x, y) in zip(inTable, outTable)}))

List Comprehensions (source : PEP 202, Wikipedia) :

List comprehensions provide a more concise way to create lists in situations where nested loops would currently be used.

print({x: y for (x, y) in zip(inTable, outTable)})
{'K': 'M', 'E': 'G', 'O': 'Q'}

About zip :

print({ord(x): y for (x, y) in zip(inTable, outTable)})
{75: 'M', 69: 'G', 79: 'Q'}
inTable = 'abcdefghijklmnopqrstuvwxyz'
outTable = 'cdefghijklmnopqrstuvwxyzab'
message = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle gr gl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."
print(message.translate({ord(x): y for (x, y) in zip(inTable, outTable)}))
i hope you didnt translate it by hand. thats what computers are for. doing it in by hand is inefficient and that's why this text is so long. using string.maketrans() is recommended. now apply on the url.
A smarter solution to build the inTable and outTable strings :
import string
inTable = string.ascii_lowercase
outTable = string.ascii_lowercase[2:] + string.ascii_lowercase[:2]

Next level :

Level 2 :

The question is in the HTML source :

find rare characters in the mess below:


The Bash answer :

localFile='./level02.txt'; >"$localFile"; wget -O "$localFile"; sed -i '38,1257 !d' "$localFile"; sed -ri 'sa[][)({}@%&$_+*^#!]aag' "$localFile"; sed -i '/^$/ d' "$localFile"; tr '\n' '\0' < "$localFile"

Next level :


How to transform an XML file using XSLT in Python ?

  1. Install requirements, as root :
    apt install libxml2-dev libxslt-dev
    Otherwise, the next step will fail complaining make sure the development packages of libxml2 and libxslt are installed.
  2. Install lxml, the library that will do the job (still as root) :
    pip-3.2 install lxml
    /usr/bin/pip-3.2 is Python's pip for Python 3.2, since I have both 2.x and 3.x versions installed.
  3. Then write a transcoding script such as :
    #!/usr/bin/env python3
    import lxml.etree as ET
    dom = ET.parse('data.xml')
    xslt = ET.parse('stylesheet.xsl')
    transform = ET.XSLT(xslt)
    newdom = transform(dom)
    print(ET.tostring(newdom, pretty_print=True))
    Due to Python's byte objects, print's internal logic and things that are not yet clear to me (and Python3.x limitations on lxml side ?), this script outputs a "byte object string", that I'll clean (the ugliest possible way) with sed :
    • changing \n into [newline]
    • changing \t into [tab]
    • changing \' into '
  4. Don't forget to move all the related .xsl files to the work directory
  5. Then run the script : python3 ./ | sed -r 's_^..(.*).$_\1_g' | sed -r 's_\\n_\n_g' | sed -r 's_\\t_\t_g' | sed -r "s_\\\'_'_g" > output.html
  6. Enjoy !

How to compute date and time ?

import datetime
from datetime import datetime, timedelta
timeFormat = '%Y-%m-%d %H:%M:%S'
now =
future = now + timedelta(seconds=500)

How to install packages for Python3 with pip ?

apt install python3-pip; pip3 install requests