Hash, HMAC and encryption: securing a Django token

Hash, HMAC and encryption: securing a Django token

A == comparison on a hash is not enough to pick the right mechanism. sha256, HMAC, salted hash, encryption: each approach offers different guarantees. Understanding which ones changes concretely how you store and verify a token in Django. Simple hash import hashlib token_hash = hashlib.sha256(token.encode()).hexdigest() A simple hash is deterministic: the same input always produces the same output. No server secret is involved. It is impossible to recover the original token from the hash (sha256 is a one-way function). But if someone knows or guesses the token, they can recompute the hash and compare. ...

May 25, 2026 · 4 min · Anthony
Python collections : Counter, defaultdict, deque and the rest

Python collections : Counter, defaultdict, deque and the rest

The Python collections module has been part of the standard library since version 2.4. It provides specialized data structures that solve recurring problems without any external dependency. Yet many developers keep writing counting loops, conditional key initialization, or Point classes with x, y, z fields when Counter, defaultdict, and namedtuple do exactly that, better and more readably. Here are the six structures I use regularly, with the cases where they actually make a difference. ...

May 22, 2026 · 7 min · Anthony
Python shutil: copy, move and archive files without subprocess

Python shutil: copy, move and archive files without subprocess

When you need to copy a directory, move files, or create an archive in Python, the temptation is to reach for subprocess.run(["cp", "-r", ...]) or os.system("mv ..."). That approach is fragile, non-portable, and unnecessary: shutil (shell utilities) has been in the Python standard library since version 2.3 and handles all of this cleanly. The Python shutil library is the go-to tool for high-level filesystem operations. Why shutil instead of os or subprocess os exposes low-level system calls: rename, link, create directories. It does not copy file contents. os.rename() fails when the source and destination are on different filesystems (separate partitions, mounted Docker volumes, etc.). subprocess with cp or mv does not work on Windows. ...

May 21, 2026 · 4 min · Anthony
Python operator: itemgetter, attrgetter and the art of replacing lambdas

Python operator: itemgetter, attrgetter and the art of replacing lambdas

The operator library has been part of Python’s standard library forever, and yet many developers keep writing lambda x: x[0] or lambda obj: obj.name when an operator function would do the same job, faster and more readably. Understanding what this library offers, and how it is implemented, changes the way you write functional code in Python. What operator contains operator exposes functions that match the language’s operators. operator.add(2, 3) is the functional equivalent of 2 + 3, operator.lt(a, b) corresponds to a < b. The point is not to replace operators in ordinary arithmetic code, that would be absurd. The point is being able to pass an operation as an argument to a higher-order function (map, filter, sorted, reduce, functools.partial). ...

May 20, 2026 · 7 min · Anthony
Python dataclasses: field(default_factory) in depth

Python dataclasses: field(default_factory) in depth

Python dataclasses automatically generate __init__, __repr__, and __eq__ from type annotations. The moment an attribute needs a mutable default value (list, dict, set), you hit a fundamental Python pitfall. field(default_factory=...) is the solution, and understanding why it is necessary changes how you reason about initialization in Python. The mutable default value trap In Python, default parameter values are evaluated once at function definition time, not at each call. This is a language property, not a bug. ...

May 19, 2026 · 5 min · Anthony
Django: save() skips full_clean() — the validation lifecycle

Django: save() skips full_clean() — the validation lifecycle

Calling obj.save() after defining validators and a clean() method on the model gives the impression that validation is guaranteed. It is not. Django does not call full_clean() during a save(), and this behavior is intentional. Understanding why changes how you architect validation in a project. What save() actually does The lifecycle of a save() call is shorter than you might expect: pre_save signal sent field.pre_save() called on each field (auto_now, auto_now_add, etc.) INSERT or UPDATE SQL based on whether a pk exists post_save signal sent No validation appears anywhere in this sequence. No blank check, no max_length, no call to clean(). The same applies to Model.objects.create() and Model.objects.bulk_create(): all three methods persist without validating. (bulk_create() is covered in depth in Django in_bulk() and bulk_create() if you work with bulk inserts.) ...

May 18, 2026 · 5 min · Anthony
Python __add__ and __iadd__: copy or in-place mutation

Python __add__ and __iadd__: copy or in-place mutation

__add__ and __iadd__ define two distinct behaviors for addition in Python: one creates a new object, the other modifies the existing one. This distinction has real consequences for aliases and shared references, and it catches even experienced developers off guard. add: the addition that creates __add__ is called by the + operator. It must return a new object and leave the operands unchanged. class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) a = Vector(1, 2) b = Vector(3, 4) c = a + b # new Vector(4, 6) print(a.x, a.y) # 1 2 — a is unchanged print(id(a) == id(c)) # False — distinct objects a + b calls a.__add__(b). If __add__ is not defined on a or returns NotImplemented, Python tries b.__radd__(a). ...

May 15, 2026 · 4 min · Anthony
Timing attacks in Django with constant_time_compare

Timing attacks in Django with constant_time_compare

A == comparison on a token looks harmless. In practice, it leaks a measurable piece of information: execution time varies depending on how many characters match. That is the principle behind a timing attack, and it is enough for an attacker to reconstruct the token one character at a time. The problem: the comparison that stops too early Python compares strings character by character and stops as soon as a mismatch is found. ...

May 14, 2026 · 3 min · Anthony
Materialized views vs Django cache for slow queries

Materialized views vs Django cache for slow queries

The instinctive response to a slow reporting endpoint is often cache. A @cache_page, a cache.set(), and the problem seems to vanish until the next expiration. This approach has a structural limitation that PostgreSQL materialized views solve at the root. The problem with cache on analytics endpoints Django cache stores the result of a Python view. The expensive SQL query still runs on every cache expiration. For a report built on multiple JOINs and aggregations, this means the first user after each cache miss waits several seconds. ...

May 13, 2026 · 4 min · Anthony
Python @property: from encapsulation to descriptors

Python @property: from encapsulation to descriptors

Accessing r.width and writing r.width = 15 with the same syntax as a plain attribute, while running validation or computation under the hood: that is what @property provides. And when that logic needs to be shared across multiple classes, descriptors come into play. @property: getters and setters without friction @property lets you expose a computed or validated attribute with the same syntax as a regular one. class Rectangle: def __init__(self, width: float, height: float): self._width = width self._height = height @property def width(self) -> float: return self._width @width.setter def width(self, value: float) -> None: if value <= 0: raise ValueError("Width must be positive.") self._width = value @property def area(self) -> float: return self._width * self._height The concrete benefit: the public API does not change. Adding validation to an existing attribute breaks no calling code. @property without a setter creates a read-only attribute. @name.deleter handles deletion via del. ...

May 12, 2026 · 3 min · Anthony

Newsletter

Get new articles delivered straight to your inbox.

No spam. Unsubscribe in one click.