14 Jun 2026

feedDjango community aggregator: Community blog posts

Understanding Memory Usage in Django Webserver Workers

If you are coming from the PHP world, you might be used to thinking that when a request reaches the web server, everything is parsed and processed from scratch. In Python, however, the behavior is a little different.

A Python web server (for example, Gunicorn) starts one or more worker processes and then continuously accepts and processes requests as a running server application. In this article, I will explore how memory is managed in that environment.

Startup time vs. request execution

When you run a Django web server (either the development server or a Gunicorn-based deployment), there are two phases of execution: startup, when the server application is initialized, and the request/response cycle, when a request is received and processed to return a response.

During startup, wsgi.py and get_wsgi_application() are executed once per worker at boot time, Django settings are evaluated, applications listed in INSTALLED_APPS are imported and registered, ready() methods of app configs are called, and URL patterns are compiled.

During request execution, the middleware chain processes the request, the URL is resolved, the view is executed, context managers run within the view, the template is rendered, the middleware chain processes the response, and the response is returned.

The Django development server runs a single worker process but uses threads to handle concurrent requests. Gunicorn runs multiple worker processes (separate copies of the server application). A common rule of thumb for Gunicorn configuration is (2 × number_of_CPU_cores) + 1 workers.

Little demo of shared memory between requests

If you have mutable globals in your Django code, they will be shared within the same worker process, even across threads.

Here's a simple example for demonstration purposes (note that this is an antipattern and should never be used in production):

from django.http import JsonResponse
from django.utils.timezone import now

TIMESTAMPS = []

def show_timestamps(request):
    TIMESTAMPS.append(now())
    return JsonResponse({"timestamps": TIMESTAMPS})

The output would be (manually indented for readability):

{"timestamps": [
  "2026-06-08T19:48:42.044Z", 
  "2026-06-08T19:48:43.875Z", 
  "2026-06-08T19:48:44.776Z"
]}

If you serve this application with Gunicorn using multiple workers and open the view in several tabs, refreshing them a few times, you will see that the timestamp list keeps growing. However, because each request may be served by a different worker, the timestamps returned by each worker will differ.

How RAM is managed

1. Compilation/setup state is persistent

When Django starts and the application server is initialized, the following objects live in process memory for the lifetime of the worker process:

This memory normally remains allocated until the worker process restarts.

2. Execution state lives per-request

Each request creates objects that are local to that request's call stack-views, forms, querysets, serializers, and so on. Once the response is sent:

Common bugs to avoid

1. Mutable module-level state

A mutable object defined at the module level persists across all requests handled by that worker process and will accumulate data from every user.

BAD:

_recent_users = []

def dashboard(request):
    # leaks across requests!
    _recent_users.append(request.user.id)
    return render(
        request, 
        "dashboard.html", 
        {"recent": _recent_users}
    )

GOOD - state belongs in the DB, cache, or session:

def dashboard(request):
    recent_users = get_recent_users_from_db()
    return render(
        request, 
        "dashboard.html", 
        {"recent": recent_users}
    )

2. Storing per-request data on a shared instance

Middleware instances live for the lifetime of the worker process. Storing anything request-specific on self means it will be shared across every request handled by that worker.

BAD:

class AuditMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # shared across all requests!
        self.current_user = None

    def __call__(self, request):
        # User A's data visible when User B's request runs
        self.current_user = request.user
        return self.get_response(request)

GOOD - attach data to the request object, which is scoped to a single request:

class AuditMiddleware:
    def __init__(self, get_response):
        # only immutable config on self
        self.get_response = get_response

    def __call__(self, request):
        # scoped to this request's lifetime
        request.current_user = request.user
        return self.get_response(request)

3. Class-level cache shared across instances

A class attribute is shared across all instances of that class and therefore across all requests. The first user's data may be cached and returned to everyone else.

BAD:

class ReportGenerator:
    # class attribute, shared across all instances and all requests!
    _cache = {}

    def get_data(self, user):
        if "data" not in self._cache:
            # first user's data cached for everyone
            self._cache["data"] = expensive_query(user)  
        return self._cache["data"]

GOOD - use the cache framework with a user-scoped key:

from django.core.cache import cache

class ReportGenerator:
    def get_data(self, user):
        key = f"report:{user.id}"
        return cache.get_or_set(
            key, 
            lambda: expensive_query(user), 
            timeout=300,
        )

Mental model

Before writing module-level code, ask yourself:

"If 1000 different users hit this worker, and this variable exists for all of them, is that safe?"

Location Lifetime Safe for user data?
Local variable in a view/function Single request ✅ Yes
request object attributes Single request ✅ Yes
threading.local() Single thread ✅ Yes
Database / cache with scoped keys Persistent but keyed ✅ Yes
self on a middleware/class instance Worker lifetime ⚠️ Only immutable config
Module-level mutable variable Worker lifetime ❌ Never user data
Class-level mutable attribute Worker lifetime ❌ Never user data

Rule of thumb: immutable configuration (numbers, booleans, strings, bytes, tuples, frozensets, and None) belongs at module level; anything that varies by request, user, or time belongs in the request cycle.

Conclusion

A Django worker process has two separate memory areas: a startup state that lives for the lifetime of the worker process (app registry, URL resolvers, middleware instances, template engines) and a request state that exists only while handling a request (views, querysets, forms) and is reclaimed by Python's garbage collector afterward.

The main rule is that immutable configuration can live at the module level, but anything that depends on the current user or request should stay inside the request cycle, such as local variables or the request object.

That's because module-level variables, class attributes, and middleware instance state are shared by all requests handled by the same worker, making them unsafe for user-specific data and a common cause of memory leaks and data exposure bugs.


Cover Picture by Jon Tyson

14 Jun 2026 5:00pm GMT

feedPlanet Python

Eli Bendersky: Plugins case study: Pluggy

Recently I came upon Pluggy, a Python library for developing plugin systems. It was originally developed as part of the pytest project - known for its rich plugin ecosystem - and later extracted into a standalone library. You're supposed to reach out for Pluggy if you want to add a plugin system to your tool or library and want to use something proven rather than rolling your own.

In this post I will share some notes on how Pluggy works, and will then review how it aligns with the fundamental concepts of plugin infrastructures.

Pluggy plug logo

Using Pluggy

Pluggy is built around the concept of hooks: functions that host applications or tools (from here on, just "hosts") expose and plugins implement. A host exposes hooks by using a decorator returned from pluggy.HookspecMarker and a plugin implements this hook using a decorator returned from pluggy.HookimplMarker.

Pluggy's documentation explains this fairly well; in this post, I'll show how to implement the htmlize tool with some plugins, introduced in the original article in my plugin series.

As a reminder, htmlize is a toy tool that takes markup notation similar to reStructuredText, and converts it to to HTML. It supports plugins to handle custom "roles" like:

some text :role:`customized text` and more text

As well as plugins that do arbitrary processing on the entire text.

Defining hooks

Out host defines two hooks:

import pluggy

hookspec = pluggy.HookspecMarker("htmlize")

@hookspec(firstresult=True)
def htmlize_role_handler(role_name):
    """Return a function accepting role contents.

    The function will be called with a single argument - the role contents, and
    should return what the role gets replaced with.
    """
    pass

@hookspec
def htmlize_contents(post, db):
    """Return a function accepting full document contents.

    The function will be called with a single argument - the document contents
    (after paragraph splitting and role processing), and should return the
    transformed contents.
    """
    pass

A hook is created by calling HookspecMarker with the project's name. This project name has to match between the host and its plugins. Pluggy is permissive about what hooks accept as parameters and what they return; for maximal flexibility and to stay true to the original htmlize example, our hooks return functions.

To accompany this HookspecMarker, the host also defines a HookimplMarker with the same name:

hookimpl = pluggy.HookimplMarker("htmlize")

This is used by plugins to attach to hooks when they're loaded.

Loading plugins in the host

The host's main function loads plugins at startup as follows:

pm = pluggy.PluginManager("htmlize")
pm.add_hookspecs(hookspecs)
pm.load_setuptools_entrypoints("htmlize")

hookspecs is our Python module containing the hooks shown above. load_setuptools_entrypoints is Pluggy's helper for loading plugins that were pip-installed into the same environment and registered as setuptools entry points. It's a way to signal - in one's setup.py or pyproject.toml file - some metadata that projects can review at runtime. In our project, the plugins register themselves with this section in the pyproject.toml file:

[project.entry-points.htmlize]
tt = "tt"

This says "for entry point htmlize, define a new entry named tt". Pluggy's load_setuptools_entrypoints then uses importlib.metadata to access this information.

Note that Pluggy doesn't require using this mechanism. Hosts can implement any plugin discovery method they want, and add plugins directly to their PluginManager with the register method. But this is the mechanism used for pytest and many other projects; it makes it very easy to automatically discover and register plugins that are installed with pip and equivalent tools.

Invoking plugins

Once PluginManager loads the plugins, invoking them is straightforward; here's how htmlize invokes the contents hooks [1]:

# Build full contents back again, and ask plugins to act on
# contents.
contents = ''.join(parts)
for handler in plugin_manager.hook.htmlize_contents(post=post, db=db):
    contents = handler(contents)
return contents

Generally, hook invocations return a list of all the hooks attached to by different plugins (a single host application can have multiple plugins installed and attaching to the same hook). When the host invokes the hook as shown above, the default order is LIFO, but plugins can affect this with hook options like tryfirst and trylast.

Implementing hooks in plugins

Here's our entire narcissist plugin that's attaching to the contents hook:

import htmlize

@htmlize.hookimpl
def htmlize_contents(post, db):
    repl = f'<b>I ({post.author})</b>'

    def hook(contents):
        return re.sub(r'\bI\b', repl, contents)

    return hook

Some notes:

  • It expects htmlize to be installed; as discussed previously, we rely on Pluggy's default install-based approach where both the host and plugins are installed into the same Python environment and can thus find each other. However, Pluggy supports any custom discovery method.
  • It uses the hookimpl exported value shown earlier.
  • It returns a function that acts on contents; this is the htmlize-specific contract (ABI, if you will) we've discussed before.

Fundamental plugin concepts in this case study

Let's see how this case study of Pluggy measures against the Fundamental plugin concepts that were covered several times on this blog.

It's important to remember that Pluggy is not a specific host application with a bespoke plugin system; rather, it's a reusable library for creating such plugin systems. Therefore, this is more of a meta case study.

Discovery

Generally, Pluggy leaves discovery logic to the user's discretion. Its PluginManager has a register method for adding plugins, and these can be discovered in any way the application chooses.

That said, Pluggy comes with one discovery mechanism built in - through the entry points process of Python packaging, as shown above. This is hugely convenient for a large number of applications, as long as both the application and its plugins are installed via standard Python packaging tools (which is a very reasonable assumption in the Python ecosystem).

Registration

In the entry point process, plugins register themselves by adding a [project.entry-points.<HOST-ID>] section in their pyproject.toml file.

Otherwise - as in the previous section - users are free to devise their own registration schemes.

Hooks

This one is easy, since it's called hooks in Pluggy parlance as well! Pluggy's implementation of hooks is rather elegant, with function decorators available for plugins to set. We've seen an example of this above with @htmlize.hookimpl decorating htmlize_contents.

Exposing an application API to plugins

Since Pluggy is designed for Python hosts and Python plugins, this one is fairly straightforward. The plugins typically assume the host project is already installed in the Python environment and its modules can be imported.

In our example, hookimpl is imported from htmlize by the plugin to accomplish this. It also shows how host data is passed to the plugin - the post and db parameters. These are APIs exposed by the host for the plugins' use.

Conclusion - is Pluggy worth it?

In footnote 2 of my original fundamental concepts of plugin infrastructures post, I wrote [2]:

This is probably why there are very few well-established plugin frameworks in existence (even in low-level languages like C or C++). It's too easy (and tempting) to roll your own.

I still believe my statement is true - plugin frameworks are very easy to create, and the functionality they provide is relatively small compared to their large surface area. In other words, this is a shallow API.

That said, Pluggy does provide some nice functionality for the more advanced uses of plugins:

  • Automatic entry point registration mechanism - if you need it
  • Signature validation
  • Consistent plugin result collection across multiple hook attachments in a single plugin and across many plugins
  • Plugin ordering with firstresult, tryfirst, trylast, etc.
  • Hook "wrappers" for some special use cases

Are these worthwhile for your project? It really depends on the project, and it's always worth keeping the tradeoff between dependencies and project effort in mind.

Code

The full code repository for this post is available here.


[1] Here plugin_manager is the value previously returned from pluggy.PluginManager; in the previous code snippet it's saved into pm - the different variable name is because a function call is made and plugin_manager is the parameter name.
[2] To be fair, that post predates the creation of Pluggy!

14 Jun 2026 10:20am GMT

Bob Belderbos: Why Rust does not need OOP

When I heard structs replace classes in Rust, I was a bit surprised. I thought, how can you do without classes? But as I started to learn Rust, I realized that structs, traits, ownership and composition help resist the temptation of OOP. In fact, Rust's approach to programming is more focused on data and behavior rather than objects.

Let's look at 5 reasons why Rust does not need OOP.

1. Composition

One major drawback of OOP is deep inheritance trees.

In classic OOP you would define class A, then class B inherits from A, then class C inherits from B, and so on. This can lead to a complex and fragile codebase where changes in one class can affect many others.

class Animal {}
class Dog extends Animal {}
class ServiceDog extends Dog {}

In What Rust Structs Taught Me About State Ownership I showed this example:

struct Tokenizer {
    chars: Vec<char>,
    position: usize,
}

impl Tokenizer {
    fn advance(&mut self) -> Option<char> {
        let ch = self.chars.get(self.position).copied();
        self.position += 1;
        ch
    }

    fn peek(&self) -> Option<char> {
        self.chars.get(self.position).copied() // char is Copy, returns a value not a reference
    }
}

We see a clear separation of data (the fields) and behavior (the methods). The Tokenizer struct holds the state, while the methods define how to interact with that state. This is a more flexible and modular approach than OOP's class-based design.

Rust also uses composition instead of inheritance. You can create complex types by combining simpler ones without the need for a class hierarchy.

struct Animal {}
struct Dog {
    animal: Animal,
}
struct ServiceDog {
    dog: Dog,
}

A great resource on this principle is Composition Over Inheritance, part of Brandon Rhodes' Python Patterns Guide.

For an example where I think OOP & inheritance went off the rails is Django's class-based views. The inheritance tree of those views is too deep making it an unpleasant API to work with, and the code so much harder to reason about. A better way is the more functional approach, see Luke Plant's Django Views - The Right Way.


2. Traits

In Python, think Protocols. In Java, think interfaces. In Rust, traits are a powerful way to define shared behavior without the need for a class hierarchy; polymorphism without inheritance.

Classic OOP:

Animal a = new Dog();
a.speak();

In Rust, you can achieve similar behavior using traits:

trait Speak {
    fn speak(&self);
}

fn make_noise(x: &impl Speak) {
    x.speak();
}

The advantage here is that you can implement the Speak trait for any type, and you don't need to have a common base class. This allows for more flexibility and code reuse.

Python has something similar with Protocols, which are part of the typing module. They allow you to define a set of methods that a class must implement, without requiring inheritance.

from typing import Protocol

class Speak(Protocol):
    def speak(self) -> None: ...

def make_noise(x: Speak) -> None:
    x.speak()

This is a more flexible alternative to ABCs (Abstract Base Classes) and allows for duck typing while still providing type safety. I wrote an article about this on Pybites.

Where OOP couples data and behavior, Rust's traits allow you to define behavior separately from data. This promotes code reuse and flexibility without the need for a rigid class structure.

Rust encourages:

struct User {}
trait Serialize {}
trait Validate {}
trait Persist {}

This is closer to the Single Responsibility Principle, the Unix philosophy of "do one thing and do it well", functional programming of data-oriented design with pure functions that operate on data, and composition over inheritance.

Hence Rust allows you to mix and match traits to create complex behavior without the need for a class hierarchy.

3. Ownership and borrowing

Many OOP patterns exist to control mutation and provide proper encapsulation. In Rust, ownership and borrowing rules ensure that data is accessed safely and efficiently.

fn process(data: Data)        // takes ownership (moved in)
fn process(data: &Data)       // borrows, read-only
fn process(data: &mut Data)   // borrows, can mutate

Just by looking at the function signature in Rust, you can understand how data is being used and modified: who owns it, who mutates it, and when it goes out of scope. It eliminates the need for patterns like getters/setters, which are often used in OOP to control access to data. And these rules are enforced at compile time, not runtime.

4. Modularity

I came to the conclusion some time ago that Python's module scope is a great feature. It allows you to organize code in a way that is more flexible than OOP's class-based organization. In Rust, modules and crates provide a way to organize code without the need for classes.

Classic OOP:

public class Counter {
    private int value;

    public void increment() {
        value += 1;
    }
}

In Rust, you can use module-level functions and structs to achieve the same result:

mod internal {
    pub struct Counter {
        value: i32,
    }

    impl Counter {
        pub fn increment(&mut self) {
            self.value += 1;
        }
    }
}

Outside:

counter.value // inaccessible
counter.increment() // ok

Yes, increment is a mutating method, the same as a setter. The win isn't avoiding methods, it's two things.

First, the privacy boundary is the module, not the object: value is hidden from the whole module, and you can hide free functions and structs too, not just wrap fields in a class.

Second, you only write the method when you need to guard an invariant. If a field is just plain data, mark it pub and read it directly, no getter ceremony. Java's idiom nudges you to wrap every field in get/set whether it needs it or not.

So encapsulation lives at the module level, not the class level. It also leans toward a more functional style where pure functions operate on data without mutable state.

In Python you can also hide functions and variables at the module level:

def _private_function():
    pass

But this does not prevent somebody importing it so you need fencing mechanisms like __all__ to control what gets imported. Python's _private is a suggestion, Rust's module privacy is enforced by the compiler.

5. Enums and pattern matching

In OOP, you often use class hierarchies to represent different types of objects. In Rust, you can use enums and pattern matching to achieve similar results without the need for a class hierarchy.

Classic OOP:

abstract class Shape {}
class Circle extends Shape {}
class Square extends Shape {}

In Rust, you can use enums to represent different shapes:

enum Shape {
    Circle(f64),
    Square(f64),
}

fn area(shape: Shape) -> f64 {
    match shape {
        Shape::Circle(r) => 3.14 * r * r,
        Shape::Square(s) => s * s,
    }
}

Not only is an enum more lightweight than a class hierarchy, it also pairs up really well with pattern matching handling different cases in a concise and (!) exhaustive manner. The compiler will not let you forget a case.

Conclusion

In short, OOP bundles data and behavior together which made a lot of sense to me for a long time. Learning Rust though, I am seeing new paradigms how the language designers have decoupled the two.

With Rust you get the good parts of OOP: encapsulation, abstraction and polymorphism, while dropping the less maintainable parts: inheritance trees, mutable state and coupling.

So to wrap up, here is a comparison of classic OOP -> Python -> Rust:

Concern Traditional OOP Python Rust
Encapsulation Classes with private/public members Modules + _private convention mod + pub visibility
Polymorphism Inheritance + virtual methods Duck typing / Protocols Traits
Reuse Inheritance Composition Composition
State modeling Class hierarchies dict, dataclass, classes struct + enum
Object lifecycle GC / constructors GC Ownership + borrowing
Error handling Exceptions Exceptions Result<T, E> + exhaustive matching

Typical style evolution

Old-school OOP:

Object → Class → Inheritance → Framework

Modern Python:

Data → Functions → Composition → Protocols

Rust:

Data → Ownership → Traits → Composition

As you see Python gets you far and is versatile, but Rust's ownership and traits give you an even more reliable and maintainable way to structure your code without the need for OOP.

Different philosophy

I am not picking on Java or OOP, I've been a fan for a long time. Studying philosophy (e.g. Plato) gives you a deeper appreciation for this way of thinking. But it can also lead to overcomplication and unnecessary coupling.

It's good to learn the different paradigms and understand their strengths and weaknesses. In summary:

Traditional OOP:

"Model the world as interacting objects."

Python:

"We trust developers."

Which is awesome, but not without risk. It allows for great flexibility and rapid development, but it can also lead to bugs and maintenance issues if not used carefully.

Rust:

"Prove correctness to the compiler."

A newer paradigm for me, but the way Rust decouples data and behavior, enforces ownership and borrowing rules, and promotes composition over inheritance, all sit well with me.

Those things do lead to more reliable and maintainable code. It encourages you to think about your code in a way that is more focused on data and behavior rather than objects.

14 Jun 2026 12:00am GMT

13 Jun 2026

feedPlanet Python

Armin Ronacher: Dangerous Technology For Americans Only

There is a bit of schadenfreude on Twitter right now about Anthropic being hit by the US government's export control directive to suspend access to Fable and Mythos. Anthropic and their leadership have spent a lot of time and effort describing its own technology as dangerous and in need of strict controls and regulation. Now that the US government appears to have taken that framing seriously and told them to turn it off for foreign nationals I can see why people are making fun of that situation.

I understand the reaction, but I urge you to not entertain it for too long because it is a giant distraction. The important part is not that Anthropic's safety language came back to bite them but the line the US government is drawing: this technology is apparently so powerful that only Americans should have it.

We are on a clear path towards a world of division. One should think that if a model is too dangerous for everyone, then it is too dangerous for Americans too. Instead the US is treating these models like weapons that need to be controlled. It is not just about capabilities, it is about racism and nationalism. If you have the wrong passport, you are not to be trusted. This is a very different thing from safety, and Europeans should pay close attention to it.

Safety and National Control

The directive, as Anthropic describes it, applies to foreign nationals whether they are inside or outside the United States, including foreign national Anthropic employees. That is an astonishing boundary if you think about it. We moved from "do not sell this model to hostile governments" to nationality itself being the defining boundary. This should be a wake-up call to Europeans in and outside the US, and quite frankly, any non US citizen.

A lot of AI safety discourse presents itself as universal: humanity, catastrophic risk, safeguards, responsible deployment. Even Anthropic's own writings start out that way, but yet every time regulation is discussed there is an overtone of national security and that it cannot get into the wrong hands. It's not just Anthropic, it's the entire US based discourse on AI. The foundation is that the US has moral superiority and others are not to be trusted. That there are other countries are authoritarian, that they lack freedoms.

That should make us uncomfortable, not just Europeans, but particularly us. It is also a situation you cannot regulate yourself out of. European technology policy is entirely unprepared for this, because this is not a question of regulation but a question of might and power, something that Europe lacks.

Europe has spent years trying to regulate large American technology companies, sometimes for good reasons. I am not reflexively against that. The DMA matters because access matters. Users should have agency over their devices, their data, and the software they run. But regulation is a useless substitute for capability and we are lacking that. Regulation might try to force open doors but if those doors only come from American or Chinese companies, then that accomplishes very little.

Also let's not be naive in that this is a negotiation of money and force. The US is in that position because the US has a mighty military. The US can bomb nations anywhere in the world, force international trade routes closed and get away with it. That's true leverage.

Oh Europe

Europe is dependent on the United States in ways that are becoming increasingly impossible to ignore. We depend on American cloud providers, operating systems, developer platforms and now AI models and internet from satellites. We also depend on global semiconductor supply chains we do not control. If access to frontier AI becomes a matter of American national security policy, Europe is not a peer in that conversation and might not even be a market.

That is a humiliating position, but one that happened entirely intentionally.

European citizens and politicians still have not managed to move beyond blaming the EU for its failures. We built and maintained fragmented markets and then pretended we had a single one. We let company formation, hiring, equity compensation, tax, notaries, KYC, banking, and cross-border services remain much harder than they need to be and we are playing these rules against each other. Not just on the European level, but within every single member state. We protect the trusts and established enterprises, who are risk averse and entrenched, instead of trusting the next generation to build great companies. We created a culture where process becomes an excuse for low agency. We made it hard to build new and large companies and then act surprised when our most ambitious founders move somewhere else or just decided to incorporate their companies in the US.

Increasingly, Europeans who want to build very large technology companies move to the United States. They do it because the capital markets are better, the startup infrastructure is better, employee equity is better understood. I cannot blame anyone doing it, and I'm guilty of this myself as we have incorporated our holding in Delaware. If you are trying to raise serious money, hire aggressively, and move quickly, the US often looks like the only game in town. Because quite frankly: it is.

But this is why we are on a dangerous death spiral already. Talent leaves because the ecosystem is weak and the ecosystem stays weak because talent leaves. Infrastructure makes the world: build excellent swimming pools and you will grow a generation of great swimmers.

The temporary task is straightforward but uncomfortable: Europeans need to believe in themselves enough not to surrender to American gravity. Moving to the US as a founder or tech employee is rational and individually it is often the right decision. But if every ambitious person treats Europe as a lost cause, then Europe becomes one. If everyone with agency leaves, the only people left to shape the system are the people most comfortable with the system as it is. Then we really should not be surprised when nothing changes.

Europe needs more ambition, more ownership, more urgency, and more willingness to build. It needs less resignation. It needs to stop confusing regulation with strategy and dependency with virtue. We need to deregulate where rules serve mostly as protectionism. We need capital markets that can fund companies at the scale modern technology requires. We need employee ownership to become normal rather than exotic. We need a real single market for services, not just speeches about one. We need countries to stop fighting each other while claiming to act in the European interest.

Most importantly: we need to stop blaming the politicians. Too many European companies are adding to that bureaucracy entirely out of their own choice. They drown you in paperwork. At one point I had to sign a four page contract for a 120 Euro lamp at an Austrian retailer, just to pick up from their store 15 minutes later. Sometimes I cannot get a speaking engagement at a European event without someone sending me complex rights waivers over. It's all just paperwork protection against potential downsides.

When we do not have the power to influence, we should at least understand why and where things are failing. Too many entrepreneurs are blaming EU regulation for failures that are originating within the member states. EU regulation is the result of a democratic process between countries that are lobbying in favor of their local industries against others in the same economic bloc. No amount of abolishment of the EU is going to fix this harsh reality. Nothing more demonstrates this as the inability for cross-border M&A in the European Union. It's not the EU that blocks it, it's the country that loses out.

Strengthening Europe is necessary because weakness makes us pawns. A Europe that cannot build, cannot finance, cannot coordinate and cannot defend its own interests will not be treated as an equal. It will be regulated around, export-controlled around, consulted after the fact or not consulted at all.

The American Trap

I do not want the lesson to be that Europe simply needs to turn itself into a copy of the United States. The US has solved some things that Europe has not. It has deep capital markets, a much stronger culture of ownership, a greater tolerance for risk, and institutions that often try to make progress possible rather than explain why it cannot happen. It also has achieved an internal level of integration that is unparalleled in Europe. Tremendous advantages!

But the American path is not obviously a healthy one in all aspects. It tends to take paths with a lot of conflict and wars, a lot of internal societal division and deep inequalities. It centralizes powers away from citizens in the presidency and people with money. You are still trading one set of failures for another. You are at the whim of the US government and its strict rules and regulations. The US barely manages to uphold the rights for its own citizens today.

We should be honest about both sides. You do not win by pretending that Europe is fine. You also do not win by pretending that America has figured everything out.

We must not be blind to all the signs of how international cooperation is falling apart around us. The US no longer talks to European governments before implementing orders that directly affect Europeans. It is threatening to take Greenland, the territory of Denmark, one of its oldest allies. Treaties, alliances and institutions have lost all their worth.

All that matters even if our own lives are focused on building companies, creating wealth, hiring people and making things. Our individual path to success is one thing, but it depends on a world where contracts work, visas work and don't change on a moment's notice, trade routes stay open, payment systems function, and families are not torn apart by border regimes or wars. If the world descends into chaos, our basic needs cannot be considered met just because we have a great salaries or equity or investors that trust us.

This is why strengthening Europe cannot be the final goal. A stronger EU is, at best, a temporary defense against a darker world and not an excuse to replace American nationalism with European nationalism. The long-term answer cannot be bigger and bigger blocs fighting over who may use which model, which chip, which cloud or which trade route.

The Way Out Is Cooperation

I'm not asking here for Europeans to get their shit together just to compete with the US or China. Maybe I hope that this is a thing that develops, but the goal absolutely cannot be that we accept the deterioration of international relationships long term.

I truly believe that Open Source matters and international cooperation matters. It is not a magical answer to every problem, but it is one of the few paths we have that does not naturally lead to total concentration of power.

If frontier AI becomes something only large corporations and governments can control, then everyone else becomes dependent on their judgment. That is a bad place to be. Corporations will optimize for their incentives, as well structured as they might be, and governments will optimize for more and more power. Right now we're on a path in which access to general-purpose capability is mediated by a small number of actors with tremendous powers.

I'm not naive in pretending AI cannot carry inherent risks. Open systems are messy, they can be misused and they create uncomfortable questions about dual-use capabilities. I do not want to wave that away but closed systems do not make those questions disappear either. Moving the power to decide into fewer hands is not a solution I believe in. And I would have the same opinion if I was a US citizen living in the US.

Any path that puts large blocs in a constant fight against each other has despicable downstream effects that result in the removal of individual rights. It's entirely pointless for the US to talk about freedoms that do not extend to non-US citizens and the same is true for Europe or any other country. We might accept these restrictions temporarily, but we absolutely cannot accept them long term for the inhumane effects that they can cause.

If we believe this technology can be used for good, then broad access matters and our goal should be to restore the international rule of law, and not to further weaken it. If we find ourselves in a war against our friends from other countries, cold or hot, we have failed as society.

The world we should be working back toward is one of international cooperation, globalization in the best sense of the word, and human dignity. The internet has made our lives irreversibly international: every day people fall in love across borders, marry across languages, move across continents, and work with friends they may never meet in countries they may never visit. Identifying too strongly with any one country in that world is a fool's errand.

Over the last decade too many of the people I got to know through Open Source were directly dragged into a war. I want to believe there is a way for us to break this cycle. We should be repairing failed states, rebuilding trust between people, and finding ways to cooperate again instead of letting the richest countries arm themselves and fight over who gets to control the future and narrative. Of course I want Europe to become stronger so it can stop being a pawn, but if we mistake that temporary need for the destination, I will be deeply disappointed.

The way out is not American supremacy, Chinese supremacy or European supremacy. The way out is to climb back toward cooperation before the alternative becomes war.

Artificial Intelligence is quickly becoming another instrument of militarization and national rivalry, when it could be one of the most powerful tools for cooperation we have. We should be using it to help people across societies and languages understand one another, not fighting over who gets to control it.

13 Jun 2026 12:00am GMT

12 Jun 2026

feedDjango community aggregator: Community blog posts

Issue 341: Django 2026 Fundraising Goals

News

DSF 2026 Fundraising Goals

The Django Software Foundation is raising its 2026 annual fundraising goal from $300,000 to $500,000. The money supports the Django Fellows program, legal and trademark work, community grants and events, ongoing infrastructure, and progress toward hiring an Executive Director.

Announcing Our DjangoCon US 2026 Talks!

DjangoCon US 2026 released its tutorial and talk lineup for Aug 24 to Aug 26, with live access for online-only ticket holders and free YouTube uploads after the conference. Expect sessions spanning Django 6.1 and modern ORM patterns, performance testing, Wagtail routing, Postgres updates, and deployment topics, with the final schedule to follow soon.

Vulnerability and malware checks in uv

uv introduces uv audit to scan locked dependencies for known vulnerabilities and adverse statuses, positioned as a faster uv-native alternative to pip-audit.


Updates to Django

Today, "Updates to Django" is presented by Hwayoung from Djangonaut Space! 🚀

Last week we had 16 pull requests merged into Django by 11 different contributors - including 5 first-time contributors! Congratulations to jodizzle, Bankai, Chris Rose, esperonus-karolis and Wes P. for having their first commits merged into Django - welcome on board!

This week's Django highlights: 🦄


Releases

Python 3.14.6 and 3.13.14 are now available!

Python 3.14.6 is out as the sixth 3.14 maintenance release, with about 179 bugfixes plus build and documentation updates since 3.14.5. Python 3.13.14 follows as the fourteenth 3.13 maintenance release, adding around 240 bugfixes along with build and documentation changes since 3.13.13.

Core Dispatch #5

Python 3.15.0 beta 2 landed June 2, with another round of milestones due June 9 and June 23. Expect the practical stuff in 3.15 including a fixed O(n^2) blowup in unicodedata.normalize, XML multi-byte encoding support, and fresh deprecation warnings around ast and abc abstract* helpers, alongside an initial documented Python security policy in the Devguide.

Sponsored Link

Middleware, but for AI agents

Django middleware composes request handlers. Harnesses do the same for AI agents - Claude Code, Codex, Gemini in one coordinated system. Learn what a harness actually is, why it's a new primitive, and how to engineer one that holds in production. Apache 2.0, open source.


Articles

My Local LLM Setup - Fast Agentic Development with No Token Bill

From Peter Grandstaff, a write-up of his maxxed-out local LLM setup using an Nvidia GeForce RTX 4090 video card. A very cool setup and one which many of us will likely be using some version of in the future.

Logical optimizations

Nested if blocks can often be rewritten as a single conjunction, but only when each if fully owns the body with no else or trailing code.

Browser Push Notifications for a Django Website

Set up browser push notifications end to end: store push subscriptions in Django, register/unregister via authenticated endpoints, and send notifications from a Huey background task using pywebpush with VAPID.

Anything new?

Maintainer Matthias Kestenholz explains why stepping away from django-mptt is hard, and how entitlement in issue trackers turns "free labor" into a burnout trap.

PyCon US 2026 - Open Source Community in Long Beach - Peter Grandstaff

Another one from Peter Grandstaff, a day-by-day PyCon US 2026 trip focused on building Django connections, sponsorship work for DjangoCon US, and catching talks on developer experience and open source community support.


Django Forum

Switch to Playwright tests for integration testing

This project aims to modernize Django's integration testing by introducing Playwright as an alternative to Selenium.

Proposal: Leverage Oracle Test Pilot for Django CI

A proposal from the Oracle Test Pilot for Third-Party Software program building Oracle Test Pilot to provide access to the latest versions of the Oracle databases to Django on GitHub, for free.


Django Fellow Reports

Natalia Bidart

This week was quite intense, with most of the focus 🔍 on getting the security release out the door 🚪. Issuing the release for the 5 CVEs took a fair amount of coordination and attention to detail, and definitely consumed a good chunk of brain power 🧠 ⚡.

Alongside that, there were a number of meetings throughout the week, so overall it was a mix of high-focus release work and keeping in sync with the different groups 🤝. Bonus: the final DEP 0018 for MAILERS was approved, moved to the accepted folder, and merged ✅.

Jacob Walls

A highlight this week was landing the listurls command modeled on django-extensions. Many tickets triaged, reviewed, authored, and discussed.


Events

Announcing Our DjangoCon US 2026 Talks!

The complete lineup of talks, August 24-26, is now live! So many great talks coming up.

Django Meetup Vol. 78 / Beyond Boilerplate: Building Maintainable CRUD in Django

Django Meetup Cologne Vol. 78 will take place on the 16th of June 2026, online and in person.


Django Job Board

Founding ML/Data Scientist (Remote, UK) at MyDataValue 🆕


Projects

django-helpdesk/django-helpdesk

A Django application to manage tickets for an internal helpdesk. Formerly known as Jutda Helpdesk.

emmett-framework/granian

A Rust HTTP server for Python applications.

12 Jun 2026 3:00pm GMT

10 Jun 2026

feedDjango community aggregator: Community blog posts

Running Fallout London on Bazzite

I'm a huge Fallout fan, and Fallout London is one of the most impressive mods I've seen in years: a full DLC-sized expansion set in post-apocalyptic London, made by a community team. Running it on Bazzite (my gaming OS of choice) wasn't completely straightforward, so here's what actually worked for me. Consider this a note to future me, but hopefully it saves someone else an afternoon of trial and error.

What you'll need

The steps

1. Install Heroic Games Launcher

If you don't have it yet, install Heroic from the Bazzite app store or via Flatpak. It's a fantastic open-source launcher that handles GOG, Epic, and Amazon games, and it plays very nicely with Proton.

After you install it, login with your GOG account

Heroic Games Launcher main screen

2. Install the Fallout London One Click Mod

Search for "Fallout London" in Heroic and install the One Click Mod version. This bundles everything together so you don't have to manually manage mod files. Let it do its thing.

Fallout London in Heroic Games Launcher

3. Disable UMU (yes, it needs to be disabled)

This is the counterintuitive part. Once the mod is installed, go to its settings in Heroic, then the Advanced tab. You'll see an option called "Disable UMU". Enable it (meaning: check the checkbox to disable UMU). I know, "enable the disable" is a confusing way to phrase it, but that's what it says.

Without this, the game won't launch correctly on Bazzite.

Heroic advanced settings showing Disable UMU option

4. Run it once in Desktop Mode

Before adding it to Steam, launch the game once directly from Heroic while you're in Desktop Mode. This lets everything install and configure properly: shaders, redistributables, the works. Wait until you're actually in the game and confirmed it runs without issues, then close it.

5. Add to Steam

Click the three-dot menu on the game in Heroic and select "Add to Steam". From this point on you can launch it from Game Mode like any other game in your library.

Adding Fallout London to Steam via Heroic

Play!

That's it. Boot into Game Mode, find Fallout London in your library, and enjoy one of the best Fallout experiences made outside of Bethesda.

Fallout in Steam
Fallout London title screen

See you in the next one!

10 Jun 2026 5:00am GMT

09 Jun 2026

feedPlanet Twisted

Hynek Schlawack: How to Ditch Codecov for Python Projects

Codecov's unreliability breaking CI on my open source projects has been a constant source of frustration for me for years. I have found a way to enforce coverage over a whole GitHub Actions build matrix that doesn't rely on third-party services.

09 Jun 2026 12:00am GMT

22 May 2026

feedPlanet Twisted

Glyph Lefkowitz: Opaque Types in Python

Let's say you're writing a Python library.

In this library, you have some collection of state that represents "options" or "configuration" for a bunch of operations. Such a set of options is a bundle of potentially ever-increasing complexity. Thus, you will want it to have an extremely minimal compatibility surface, with a very carefully chosen public interface, that is either small, or perhaps nothing at all. Such an object conveys state and might have some private behavior, but all you want consumers to be able to do is build it in very constrained, specific ways, and then pass it along as a parameter to your own APIs.

By way of example, imagine that you're wrapping a library that handles shipping physical packages.

There are a zillion ways to do it ship a package. There are different carriers who can ship it for you. There's air freight, and ground freight, and sea freight. There's overnight shipping. There's the option to require a signature. There's package tracking and certified mail. Suffice it to say, lots of stuff.

If you are starting out to implement such a library, you might need an object called something like ShippingOptions that encapsulates some of this. At the core of your library you might have a function like this:

1
2
3
4
5
async def shipPackage(
        how: ShippingOptions,
        where: Address,
    ) -> ShippingStatus:
    ...

If you are starting out implementing such a library, you know that you're going to get the initial implementation of ShippingOptions wrong; or, at the very least, if not "wrong", then "incomplete". You should not want to commit to an expansive public API with a ton of different attributes until you really understand the problem domain pretty well.

Yet, ShippingOptions is absolutely vital to the rest of your library. You'll need to construct it and pass it to various methods like estimateShippingCost and shipPackage. So you're not going to want a ton of complexity and churn as you evolve it to be more complex.

Worse yet, this object has to hold a ton of state. It's got attributes, maybe even quite complex internal attributes that relate to different shipping services.

Right now, today, you need to add something so you can have "no rush", "standard" and "expedited" options. You can't just put off implementing that indefinitely until you can come up with the perfect shape. What to do?

The tool you want here is the opaque data type design pattern. C is lousy with such things (FILE, pthread_*_t, fd_set, etc). A typedef in a header file can easily achieve this.

But in Python, if you expose a dataclass - or any class, really - even if you keep all your fields private, the constructor is still, inherently, public. You can make it raise an exception or something, but your type checker still won't help your users; it'll still look like it's a normal class.

Luckily, Python typing provides a tool for this: typing.NewType.

Let's review our requirements:

  1. We need a type that our client code can use in its type annotations; it needs to be public.
  2. They need to be able to consruct it somehow, even if they shouldn't be able to see its attributes or its internal constructor arguments.
  3. To express high-level things (like "ship fast") that should stay supported as we add more nuanced and complex configurations in the future (like "ship with the fastest possible option provided by the lowest-cost carrier that supports signature verification").

In order to solve these problems respectively, we will use:

  1. a public NewType, which gives us our public name...
  2. which wraps a private class with entirely private attributes, to give us an actual data structure, while not exposing the constructor,
  3. a set of public constructor functions, which returns our NewType.

When we put that all together, it looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from dataclasses import dataclass
from typing import Literal, NewType

@dataclass
class _RealShipOpts:
    _speed: Literal["fast", "normal", "slow"]

ShippingOptions = NewType("ShippingOptions", _RealShipOpts)

def shipFast() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts("fast"))

def shipNormal() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts("normal"))

def shipSlow() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts("slow"))

As a snapshot in time, this is not all that interesting; we could have just exposed _RealShipOpts as a public class and saved ourselves some time. The fact that this exposes a constructor that takes a string is not a big deal for the present moment. For an initial quick and dirty implementation, we can just do checks like if options._speed == "fast" in our shipping and estimation code.

However, the main thing we are doing here is preserving our flexibility to evolve the related APIs into the future, so let's see how we might do that. For example, let's allow the shipping options to contain a concrete and specific carrier and freight method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from dataclasses import dataclass
from enum import Enum, auto
from typing import NewType

class Carrier(Enum):
    FedEx = auto()
    USPS = auto()
    DHL = auto()
    UPS = auto()

class Conveyance(Enum):
    air = auto()
    truck = auto()
    train = auto()

@dataclass
class _RealShipOpts:
    _carrier: Carrier
    _freight: Conveyance

ShippingOptions = NewType("ShippingOptions", _RealShipOpts)

def shipFast() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts(Carrier.FedEx, Conveyance.air))

def shipNormal() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts(Carrier.UPS, Conveyance.truck))

def shipSlow() -> ShippingOptions:
    return ShippingOptions(_RealShipOpts(Carrier.USPS, Conveyance.train))

def shippingDetailed(
    carrier: Carrier, conveyance: Conveyance
) -> ShippingOptions:
    return ShippingOptions(_RealShipOpts(carrier, conveyance))

As a NewType, our public ShippingOptions type doesn't have a constructor. Since _RealShipOpts is private, and all its attributes are private, we can completely remove the old versions.

Anything within our shipping library can still access the private variables on ShippingOptions; as a NewType, it's the same type as its base at runtime, so it presents minimal1 overhead.

Clients outside our shipping library can still call all of our public constructors: shipFast, shipNormal, and shipSlow all still work with the same (as far as calling code knows) signature and behavior.

If you need to build and convey some state within your public API, while avoiding breakages associated with compatibility churn, hopefully this technique can help you do that!


Acknowledgments

Thanks for reading, and thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor.


  1. The overhead is minimal, but it is not completely zero. The suggested idiom for converting to a NewType is to call it like a function, as I've done in these examples, but if you are wanting to use this pattern inside of a hot loop, you can use # type: ignore[return-value] comments to avoid that small cost.

22 May 2026 12:33am GMT

04 Apr 2026

feedPlanet Twisted

Donovan Preston: Using osascript with terminal agents on macOS

Here is a useful trick that is unreasonably effective for simple computer use goals using modern terminal agents. On macOS, there has been a terminal osascript command since the original release of Mac OS X. All you have to do is suggest your agent use it and it can perform any application control action available in any AppleScript dictionary for any Mac app. No MCP set up or tools required at all. Agents are much more adapt at using rod terminal commands, especially ones that haven't changed in 30 years. Having a computer control interface that hasn't changed in 30 years and has extensive examples in the Internet corpus makes modern models understand how to use these tools basically Effortlessly. macOS locks down these permissions pretty heavily nowadays though, so you will have to grant the application control permission to terminal. But once you have done that, the range of possibilities for commanding applications using natural language is quite extensive. Also, for both Safari and chrome on Mac, you are going to want to turn on JavaScript over AppleScript permission. This basically allows claude or another agent to debug your web applications live for you as you are using them.In chrome, go to the view menu, developer submenu, and choose "Allow JavaScript from Apple events". In Safari, it's under the safari menu, settings, developer, "Allow JavaScript from Apple events". Then you can do something like "Hey Claude, would you Please use osascript to navigate the front chrome tab to hacker news". Once you suggest using OSA script in a session it will figure out pretty quickly what it can do with it. Of course you can ask it to do casual things like open your mail app or whatever. Then you can figure out what other things will work like please click around my web app or check the JavaScript Console for errors. Another very important tips for using modern agents is to try to practice using speech to text. I think speaking might be something like five times faster than typing. It takes a lot of time to get used to, especially after a lifetime of programming by typing, but it's a very interesting and a different experience and once you have a lot of practice It starts to to feel effortless.

04 Apr 2026 1:31pm GMT