Aller au contenu

Python⚓︎

Voisinage⚓︎

🐣 2022-08

Lorsqu’on parcoure une liste, il est parfois utile d’avoir accès à l’élément précédent et au suivant, pour faire par exemple des liens suivant/précédent entre des billets de blog.

Cette implémentation permet de spécifier le premier et le dernier item de la liste au besoin, ce qui permet de lier vers d’autres contenus par exemple.

neighborhood.py
def neighborhood(iterable, first=None, last=None):  # (1)!
    """
    Yield the (previous, current, next) items given an iterable.
    You can specify a `first` and/or `last` item for bounds.
    """
    iterator = iter(iterable)
    previous = first
    current = next(iterator)  # Throws StopIteration if empty.
    for next_ in iterator:
        yield (previous, current, next_)
        previous = current
        current = next_
    yield (previous, current, last)
  1. 🙋‍♂️ iterable peut être une liste, un set ou toute structure… itérable !

Tout se passe au moment du yield, on garde en mémoire le précédent et le courant lors du parcours de l’itérateur et… on n’oublie pas le dernier !

Lister des dossiers/fichiers⚓︎

🐣 2022-08

Il est courant en Python de devoir parcourir des fichiers ou des dossiers. Ces deux méthodes permettent de récupérer les chemins des dossiers ou des fichiers à partir d’une racine.

each-from.py
import fnmatch
import os


def each_folder_from(source_dir, exclude=None):
    """Walk across the `source_dir` and return the folder paths."""
    for direntry in os.scandir(source_dir):
        if direntry.is_dir():
            if exclude is not None and direntry.name in exclude:
                continue
            yield direntry


def each_file_from(source_dir, pattern="*", exclude=None):  # (1)
    """Walk across the `source_dir` and return the matching `pattern` file paths."""
    for filename in fnmatch.filter(sorted(os.listdir(source_dir)), pattern):
        if exclude is not None and filename in exclude:
            continue
        yield source_dir / filename
  1. Le paramètre de pattern est bien pratique pour ne récupérer que des *.md par exemple.

Générer un md5⚓︎

🐣 2022-08

Parce que j’oublie à chaque fois comment faire…

md5.py
import hashlib


def generate_md5(content):
    """Generate a md5 string from a given string.

    >>> generate_md5("foo")
    'acbd18db4cc2f85cedef654fccc4a4d8'
    """
    return hashlib.md5(content.encode()).hexdigest()

Il y aurait probablement de meilleurs algorithmes à utiliser selon les cas.

Générer une chaîne de 128 bits⚓︎

🐣 2022-08

Pour avoir des slugs dans une URL par exemple.

128bits-string.py
import base64
import uuid


def generate_128bits():
    """Generate a 128bits string, useful for URLs.

    >>> generate_128bits()
    'LQ0HB7ksTdesCuSms-I98Q'
    """
    return base64.urlsafe_b64encode(uuid.uuid4().bytes)[:22].decode()

Pour un usage réel, il faudrait exclure certains caractères qui peuvent prêter à confusion. Mais qui dicte des URLs ?

Générer une data URI⚓︎

🐣 2022-08

Il est possible de transformer des images en chaîne de caractère afin de les insérer directement dans le HTML.

data-uri.py
import base64
import mimetypes


def img_to_data(path):
    """Convert a file (specified by a path) into a data URI."""
    mime, _ = mimetypes.guess_type(path)
    with open(path, "rb") as fp:
        data = fp.read()
        data64 = "".join(base64.encodestring(data).splitlines())
        return f"data:{mime};base64,{data64}"

Utiliser des ULID (vs. UUID)⚓︎

🐣 2022-09

Pour obtenir de l’unicite (pour des clés primaires par exemple), mieux que des UUID, il y a les ULID :

Récupérer la valeur epoch depuis une date ISO⚓︎

Parce que j’ai été surpris que ce soit aussi compliqué. Je suis peut-être passé à côté d’un truc plus élégant…

to-epoch.py
import time
from datetime import date


def iso8601toEpoch(iso8601):
    """The name says it all, there has to be a better way.

    >>> iso8601toEpoch('2021-07-11')
    1625976000.0
    """
    return time.mktime(
        date(*[int(part) for part in iso8601.split("-")]).timetuple()
    )  # fmt: off (1)
  1. Désactivation de black : on veut faire rentrer le code dans la page manuellement.

TODO

Configurer black pour avoir des lignes plus courtes par défaut.

📱 Mot de passe à usage unique⚓︎

🐣 2022-10

Un bout de code qui vient de SourceHut pour générer un mot de passe à usage unique basé sur le temps.

totp-2fa.py
import base64
import hashlib
import hmac
import struct
import time


def totp(secret, token):
    tm = int(time.time() / 30)
    key = base64.b32decode(secret)

    for ix in range(-2, 3):
        b = struct.pack(">q", tm + ix)
        hm = hmac.HMAC(key, b, hashlib.sha1).digest()
        offset = hm[-1] & 0x0F
        truncatedHash = hm[offset : offset + 4]
        code = struct.unpack(">L", truncatedHash)[0]
        code &= 0x7FFFFFFF
        code %= 1000000
        if token == code:
            return True

    return False

Je ne comprends pas tout mais j’espère pouvoir m’en resservir un jour (et le comprendre à ce moment là…).

La version anglaise que je ne me risque pas à frangliser :

The algorithm is as follows:

  1. Divide the current Unix timestamp by 30
  2. Encode it as a 64-bit big endian integer
  3. Write the encoded bytes to a SHA-1 HMAC initialized with the TOTP shared key
  4. Let offs = hmac[-1] & 0xF
  5. Let hash = decode hmac[offs .. offs + 4] as a 32-bit big-endian integer
  6. Let code = (hash & 0x7FFFFFFF) % 1000000
  7. Compare this code with the user’s code

You’ll need a little dependency to generate QR codes with the otpauth:// URL scheme, a little UI to present the QR code and store the shared secret in your database, and a quick update to your login flow, and then you’re good to go.

This implementation has a bit of a tolerance added to make clock skew less of an issue, but that also means that the codes are longer-lived.

Tâches asynchrones⚓︎

🐣 2022-09

Surtout des liens vers les projets existants dont j’ai toujours du mal à me rappeler :

  • RQ (Redis Queues) : is a simple Python library for queueing jobs and processing them in the background with workers.
  • Procrastinate : PostgreSQL-based Task Queue for Python
  • wakaq : Distributed background task queue for Python backed by Redis, a super minimal Celery
  • Dramatiq : Dramatiq is a background task processing library for Python with a focus on simplicity, reliability and performance.
  • huey : a lightweight alternative.
  • Nameko : A microservices framework for Python that lets service developers concentrate on application logic and encourages testability.
  • Background Tasks : Starlette includes a BackgroundTask class for in-process background tasks. A background task should be attached to a response, and will run only once the response has been sent.
  • Chard : is a very simple async/await background task queue for Django. One process, no threads, no other dependencies.

Pour jouer⚓︎

🐣 2022-11


Dernière mise à jour: 2022-11-09