skip to navigation
skip to content

Planet Python

Last update: October 16, 2025 01:43 PM UTC

October 15, 2025


Real Python

Polars vs pandas: What's the Difference?

Polars and pandas both provide DataFrame-based data analysis in Python, but they differ in syntax, performance, and features. In this tutorial on Polars vs pandas, you’ll compare their method chaining styles, run timed performance tests, explore LazyFrame optimizations in Polars, convert data between the two libraries, and create plots with their built-in tools. You’ll also examine scenarios where each library’s strengths make it the better choice.

By the end of this tutorial, you’ll understand that:

  • Polars expressions and contexts let you build clear, optimized query pipelines without mutating your original data.
  • LazyFrames with query optimization in Polars can outperform pandas for grouped and aggregated workloads.
  • Streaming in Polars enables processing datasets that don’t fit in memory, which pandas can’t handle natively.
  • .to_pandas() and from_pandas() let you convert between DataFrame formats, and Narwhals offers a library-agnostic API.
  • Built-in plotting uses Altair for Polars and Matplotlib for pandas, allowing quick visualization directly from DataFrames.

To get the most out of this tutorial, it’s recommended that you already have a basic understanding of how to work with both pandas and Polars DataFrames, as well as Polars LazyFrames.

To complete the examples in this tutorial, you’ll use various tools and the Python REPL. You’ll use the command line to run some scripts that time your code and reveal how pandas and Polars compare. You’ll also take advantage of the plotting capabilities of Jupyter Notebook.

Much of the data you’ll use will be random and self-generated. You’ll also use a cleansed and reformatted Apache Parquet version of some freely available retail data from the UC Irvine Machine Learning Repository. Parquet files are optimized to store data and analyze it efficiently. This enables you to achieve optimal performance from the pandas and Polars libraries.

Before you start, you should download the online_retail.parquet file from the tutorial downloadables and place it into your project directory.

You’ll need to install the pandas and Polars libraries, as well as PyArrow, Matplotlib, Vega-Altair, and Narwhals, to make sure your code has everything it needs to run. You’ll also use NumPy, which is currently installed automatically when you install pandas.

You may also want to consider creating your own virtual environment within your project folder to install the necessary libraries. This will prevent them from interfering with your current setup.

You can install the required libraries using these commands at your command prompt:

Shell
$ python -m pip install polars \
                        pandas \
                        pyarrow \
                        narwhals \
                        altair \
                        jupyterlab \
                        matplotlib

All the code examples are provided in the downloadable materials for this tutorial, which you can download by clicking the link below:

Get Your Code: Click here to download the free sample code you’ll use to learn the differences between Polars and pandas.

Now that you’re set up, it’s time to get started and learn about the main differences between Polars and pandas.

Take the Quiz: Test your knowledge with our interactive “Polars vs pandas: What's the Difference?” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Polars vs pandas: What's the Difference?

Take this quiz to test your knowledge of the Polars vs pandas tutorial and review the key differences between these open-source Python libraries.

Do Polars and pandas Use the Same Syntax?

There are similarities between Polars and pandas. For example, they both support Series and DataFrames and can perform many of the same data analysis computations. However, there are some differences in their syntax.

To explore this, you’ll use the order details in your online_retail.parquet file to analyze both pandas and Polars DataFrames. This file contains the following data:

Column Name Description
InvoiceNo Invoice number
StockCode Stock code of item
Description Item description
Quantity Quantity purchased
InvoiceDate Date invoiced
UnitPrice Item price
CustomerID Customer identifier
Country Country of purchase made

Next, you’ll analyze some of this data with pandas and then with Polars.

Using Index-Based Syntax in pandas

Suppose you want a DataFrame with a new Total column that contains the total cost of each purchase. You also want to apply filtering so you can concentrate on specific data.

To achieve this, you might write the following pandas code in your REPL:

Python pandas_polars_demo.py
>>> import pandas as pd

>>> orders_pandas = pd.read_parquet("online_retail.parquet")

>>> orders_pandas["Total"] = (
...     orders_pandas["Quantity"] * orders_pandas["UnitPrice"]
... )

>>> orders_pandas[["InvoiceNo", "Quantity", "UnitPrice", "Total"]][
...     orders_pandas["Total"] > 100
... ].head(3)
    InvoiceNo  Quantity  UnitPrice  Total
46     536371        80       2.55  204.0
65     536374        32      10.95  350.4
82     536376        48       3.45  165.6

This code uses pandas index-based syntax, inspired by NumPy, on which pandas was originally built. First, you add a new Total column to your DataFrame. The column is calculated by multiplying the values of the Quantity and UnitPrice columns together. This operation permanently changes your original DataFrame.

Read the full article at https://realpython.com/polars-vs-pandas/ »


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

October 15, 2025 02:00 PM UTC

Quiz: Polars vs pandas: What's the Difference?

In this quiz, you’ll test your understanding of the Polars vs pandas: What’s the Difference? tutorial. Most of the answers are somewhere in the tutorial, but you may need to use some of the links to clarify some of the questions.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

October 15, 2025 12:00 PM UTC


Lucas Cimon

Spotlight on pdfly, the Swiss Army knife for PDF files

pdfly logo

Project documentation: pdfly.readthedocs.io

pdfly is the youngest project of the py-pdf organization. It has been created by Martin Thoma in 2022.

It's simply a CLI tool to manipulate PDF files, written in Python and based on the fpdf2 & pypdf libraries.

I'm a maintainer of the project 🙂

What can it do & what's next?

Find out by reading the full article


Permalink

October 15, 2025 11:48 AM UTC


Seth Michael Larson

Re(blog, tweet, toot, skoot, skeеt, post)

Have you noticed the similar terms used for sharing someone else's content with attribution from your own account? Reblogging was the original term for “blogging” another user's content, first developed by project “reBlog” and popularized by Tumblr. Remember that “blog” is a truncation of “weblog” (... should it have been ’blog to capture the shortening?)

Anyway, here's a railroad diagram of all the different words I could think of:

re blog t wee oo Twitter & Mastodon sk ee oo Bluesky t post boost path { stroke-width: 3; stroke: black; fill: rgba(0,0,0,0); } text { font: bold 14px monospace; text-anchor: middle; white-space: pre; } text.diagram-text { font-size: 12px; } text.diagram-arrow { font-size: 16px; } text.label { text-anchor: start; } text.comment { font: italic 12px monospace; } g.non-terminal text { /*font-style: italic;*/ } rect { stroke-width: 3; stroke: black; fill: hsl(120,100%,90%); } rect.group-box { stroke: gray; stroke-dasharray: 10 5; fill: none; } path.diagram-text { stroke-width: 3; stroke: black; fill: white; cursor: help; } g.diagram-text:hover path.diagram-text { fill: #eee; }

Tweet was coined for Twitter, a “micro-blogging” platform, so retweets were what reblogging was called on that platform. That naming has since changed, but wouldn't you know it: tweets used to be known as “twits”. Given Twitters owner that name makes more sense now than ever.

Toots” are an elephant-themed “tweet” for Mastodon, with “boost” being the official term. “Skoot” was initially proposed for Bluesky, but cleverer folks suggested “skeеt”, much to Jay's dismay (stop trying to make skoot happen).

Now you see less “platform-specific” terminology being thrown around, like “post” and “repost”. Personally, I'm not a fan: check your posts at the door, we're goin’ ‘bloggin’! 🏄︎

I and many other blogs publish a “blogroll”, or a list of other blogs and pages that we've “reblogged” to our own website. If you're interested, give those a read and discover something new by surfing the web like we did in the 90s.



Thanks for keeping RSS alive! ♥

October 15, 2025 12:00 AM UTC

October 14, 2025


The Python Coding Stack

Creating a Singleton Class in Python And Why You (Probably) Don’t Need It

If you spend long enough in the programming world, you’ll come across the term singleton at some point. And if you hadn’t seen this term yet, well, now you have!

In Python, you don’t need singleton classes. Almost never. But creating one is a great exercise in understanding how Python creates objects. And discussing alternatives to a singleton class helps you explore other aspects of Python.

“Hey Stephen, sounds great, but you haven’t told us what this singleton thing is yet!”

Fair point – here we go.

What’s a Singleton?

The singleton pattern is a design pattern in which a class can produce only one instance. Not two, not three, just one. And each time you try to create a new instance of a singleton class, you get the same object again.

Let me pick a trivial example of when you may need this. You’re writing a game. Perhaps several players can play games simultaneously. And you need a leaderboard. But you only want one leaderboard. You don’t want each game to create its own leaderboard. Otherwise, what’s the point of the leaderboard?

There are other examples in programming when singletons appear to be the solution: creating a connection to a database or to a hardware device – you only want one connection – creating a logger or a configuration manager. But they sound too serious and proper. So I’ll stick with the leaderboard in a game example for this discussion.

Creating a Leaderboard • First Attempt

Let’s say you have a Game class and you create Game instances each time a game is played. I won’t write this class as it’s not needed, and I want to keep this article relatively brief (famous last words!).

The Game class needs to access a leaderboard. Each Game instance needs to access a leaderboard – the same leaderboard. Let’s say you create a class to manage this leaderboard:

All code blocks are available in text format at the end of this article • #1 • The code images used in this article are created using Snappify. [Affiliate link]

You add the .scores data attribute, which is a dictionary, in the class’s .__init__() method. If this is all you need, you definitely don’t need a class. But you add some methods to manage the leaderboard:

#2

Now you have a bit more functionality. Let’s review these methods briefly:

Looks good? Let’s try it out. For simplicity, you can just add these lines to the same script where you define your class:

#3

You create an instance of Leaderboard and call .add_score() three times. If we had a Game class, the first line, which creates a Leaderboard instance and assigns it to an identifier, would be included in the Game.__init__(), but here I’m just creating this variable in the main script. Here’s the displayed leaderboard:

Kate: 15
Stephen: 14

All seems fine. Kate is leading with 15 points. I’m second (also last) with 14 points.

But, later in your program, possibly within a different Game instance, you write the following:

#4

And here’s the output now:

Kate: 15
Stephen: 14
​
Now Dealing With ‘another_leaderboard’
Sarah: 13
Max: 7

Recall that the first two lines of the output are from the code you wrote earlier.

But where’s Kate in the new leaderboard? And where am I? This code creates a new Leaderboard object, unrelated to the previous one. You can confirm this by showing the identity of each object, using id(), or by using the is operator:

#5

The outputs from these three calls to print() are below:

4347130752
4347064592
False

The two objects have different identities. They’re not the same object. The identity values you get will be different from mine, but what matters here is that they’re different from each other.

You could make sure you don’t call Leaderboard() again in your code. But this is not always possible or straightforward. And you’d still need to make sure your documentation makes this really clear. And will your users read the documentation? Who knows. You may be opening the door to bugs.

Creating a Leaderboard • Second Attempt Using a Singleton Class

Now, you may be thinking: “Hey, I read about this great design pattern: the singleton pattern. Surely, this is a great time to use it…”.

The singleton is an important pattern in some languages. (Spoiler alert: it’s not so useful in Python – you’ll see why later). So let’s try to implement it in Python.

Let’s start with this question: “What’s the first thing that happens when you create an instance of a class in Python?”

If you answered: “Python calls its .__init__() method to initialise the object”, then you’re not quite right. Something else happens first. The .__init__() method initialises a “blank” instance of the class. But what creates that instance in the first place? It’s not .__init__().

It’s .__new__(). In most cases, you don’t need to worry about defining .__new__() when you define a class. The default behaviour is good enough. But in this case, you want to have more control over what happens when you create a new instance of the Leaderboard class:

#6

You add a few things to the class definition:

When you don’t define a .__new__() special method, Python uses the default implementation, which is inherited from the base class object. All Python classes inherit from the object class. However, since you define a .__new__() method in your class, Python uses this method instead of the default .__new__().

But then you need to call super().__new__(), which creates the new blank object using object.__new__(). However, you only do this if the ._instance class attribute is None – that’s what if cls._instance is None: does. Let’s understand what’s happening here.

The first time you create an instance of the class, you create the new blank instance since ._instance is None at first. You then assign this new instance to the class attribute ._instance. The .__new__() method should return the instance, which is then passed on to .__init__(). But we’ll get there later.

What happens if you try to create a new Leaderboard object again? The second time your code calls Leaderboard.__new__(), the class attribute ._instance is no longer None. It now refers to an object of the class. Therefore, the code bypasses the creation of a new object and returns the existing one.

So, is the problem solved? Let’s find out. Here’s a reminder of the code used to explore this class (with a few extra print() calls):

#7

Here’s the output now:

Show leaderboard:
Kate: 15
Stephen: 14
​
Show leaderboard:
Sarah: 13
Max: 7
​
4344263552
4344263552
True

There’s good news and bad news – which one do you want first?

Let’s start with the good news. The variable names leaderboard and another_leaderboard refer to the same object. Notice how the identity returned by id() is the same, and the is expression returns True. They’re the same object. When you call Leaderboard() the second time, your code doesn’t create a new instance. It uses the existing one.

Great.

But the leaderboards displayed are still different. Why?

You now have the same object – you’re not creating a new one. But you’re reinitialising it the second time you call Leaderboard(). When you call a class to create an instance by using Leaderboard() (with parentheses), Python first calls the class’s .__new__(). But you dealt with that already – it doesn’t create a new instance. However, the instance returned by .__new__() is passed to the class’s .__init__().

And here’s a reminder of what your Leaderboard.__init__() does:

#8

Your instance already exists. It already has a .scores data attribute with some data in it. However, when you call .__init__() again, you overwrite .scores with an empty dictionary. Any data you already had is gone.

So, you now need to fix this, too. What a pain:

#9

If you haven’t seen the built-in hasattr() function yet, it stands for has attribute. You pass an object and the name of an attribute, and it returns True or False depending on whether that object has that attribute!

In this case, you pass self as the first argument. And self is the name that refers to the instance you’re dealing with. You also pass the string “initialised” as the second argument.

You won’t overwrite .scores when you call Leaderboard() a second time, or a third time… You’re ensuring that an object can only be initialised once.

Run the code now:

Show leaderboard:
Kate: 15
Stephen: 14
​
Show leaderboard:
Kate: 15
Stephen: 14
Sarah: 13
Max: 7
​
4336350080
4336350080
True

There’s still only one Leaderboard object. But now, you’re not overwriting any data it already has, either. As you can see, when you call another_leaderboard = Leaderboard(), you don’t create a new instance. Instead, you reuse the one you already have. And you don’t erase its existing data, but add on to it.

Now, your class can only have one instance of the Leaderboard class. It’s a singleton!

That’s a lot of work to create a useable singleton class.

And I won’t open the can of worms of the scenario where you may have multiple concurrent threads. Two or more threads may access .__new__() at the same time. And that’s not good!


The Python Coding Stack is getting bigger and better, and not just because there are more of you reading every week. I’ll send out an email announcing more articles, more goodies and more value soon. Stay tuned.

And make sure you upgrade to a paid subscription to make sure you don’t miss anything–now is a good time to upgrade, before monthly and yearly subscription prices go up (they only go up for new paid subscribers, never for existing ones)

Subscribe now


We’ve Learnt Stuff By Creating a Singleton

Creating a singleton class, as you did above, is a useful exercise to help you understand how Python creates and initialises new objects. From a learning perspective, it’s a great exercise.

But, do you need this in your code?

Generally, no.

It’s a lot of work.

There’s an easier way to achieve the same effect in Python (which may not exist in other programming languages).

And maybe you don’t really need a single global object that you refer to from all over your program.

Let’s explore some options. And no, I won’t cover all the options. I said I want to keep this article brief (but I’m already on 2k+ words). My aim here is to start you off on a journey to think about what goes where, when, and how…

Alternative to the Singleton Class • Move The Class to a New Module

Let’s roll back the changes to the Leaderboard class. Delete the .__new__() method and the ._instance class attribute. And revert back to the original, simpler .__init__(). However, place the class in its own script. Let’s call it leaderboard.py:

#10

Note how .__new__() is no longer there and .__init__() simply creates the .scores data attribute.

There’s also one more line of code after the class definition – and only one. You create an instance of the class.

Now, let’s go back to your main script, which could be anywhere else in your program. Let’s call the main script main.py:

#11

The class defined in leaderboard.py is not a singleton class. It’s a regular class. However, you create the one and only instance you’ll ever create within leaderboard.py. Then, you simply import this instance using from leaderboard import leaderboard. The variable name and module name don’t have to be the same, of course, but in this case they are.

Ah, what if you import the module more than once? I can hear you think… Python only loads a module once. Here’s a demonstration. Add the following print() call to leaderboard.py:

#12

Now, go back to main.py and import leaderboard a second time anywhere else in your code:

#13

Run this code. How many times do you see the text leaderboard.py loaded in your output?

leaderboard.py loaded
Show leaderboard:
Kate: 15
Stephen: 14
​
Show leaderboard:
Kate: 15
Stephen: 14
Sarah: 13
Max: 7

Once. Modules are only loaded once. And the final output still shows the correct, combined leaderboard.

And there you go – you can only have one instance of Leaderboard now, without any of the hassle of messing around with .__new__() and .__init__().

Modules in Python give you a shortcut to create singleton-like behaviour.

Sometimes, You Can Simplify Further

In fact, do you really need the Leaderboard class? Sometimes, you do, or you prefer to have everything relating to the leaderboard in a single object. In that case, the solutions in the previous section and in the one after this one are great.

But can you live with a simpler option?

#14

Note that some functions’ names are updated to make them more readable since they’re no longer methods in a class.

Sure, this option may not always be possible. And some purists will scoff at these functions accessing and mutating a global variable (although you can modify the functions to accept the dictionary as one of the arguments, if you prefer).

The leading underscore in _scores indicates that you don’t intend this variable to be used by the user. It’s not meant for public use. Users should only access it through the functions.

If you prefer, you can place ._scores and the functions in a separate module and import them. As you saw earlier, Python imports a module only once. Therefore, anything defined within a module is effectively a singleton! In Python, the behaviour of modules makes creating a single instance of a class to share across your code much easier – even trivial. Other languages don’t have this option, which is why the singleton pattern exists.

So, if you think a singleton class is the solution to your problem, consider whether this simpler option will do!

Need More Flexibility and Future-Proof Code?

Here’s yet another option. Create a file called game_services.py:

#15

You can also define the Leaderboard class within this module, if you prefer, but here I’m leaving it in its own module, leaderboard.py. The GameServices class has a single data attribute, .leaderboard, which contains an instance of Leaderboard. This instance of Leaderboard is created when you create an instance of GameServices, which you do in the final line in this script.

You’re using composition in this case. The GameServices class has access to Leaderboard by having a Leaderboard instance as one of its attributes. You can read more about composition in this pair of articles: Choose Your Fighter • Let’s Play (#1 in Inheritance vs Composition Pair) and Choose Your Fighter • Inheritance vs. Composition (#2 in Inheritance vs Composition Pair).

Back in main.py, you can now import this game_services instance:

#16

At first sight, this version seems similar to the first alternative I presented above, just a bit more complex. However, instead of creating an instance of Leaderboard that is then used elsewhere, in this version, the Leaderboard instance is included in a container, the GameServices object. You then use the instance of the GameServices object wherever needed.

There’s more boilerplate code, but you also get more flexibility with this version. What if you want to replace the leaderboard with a different one for testing purposes? The classic singleton class is hard to test. This option simplifies things because you can assign a new Leaderboard to game_services.leaderboard or create a separate GameServices object for this purpose. Nothing else needs to change in your code.

You can also use a different implementation of Leaderboard, say you have an AlternativeLeaderboard class you want to experiment with. It’s easier and safer to make these changes when the leaderboard is included in the GameServices object.

And what if you later decide you want multiple leaderboards? Perhaps one for a version of the game and a different leaderboard for another version of the game? You no longer want a singleton! But with this version of the code, you can easily create another data attribute in GameServices. Sure, you’d be able to do so if using Leaderboard directly, as in the first example. But this option makes it safer and easier to expand your code.

And perhaps, you have other services you want to share, not just a leaderboard. You can also add more data attributes.


Support The Python Coding Stack


Final Words

Note how the alternatives of the singleton class use standard classes that don’t need a .__new__() and extra work in the .__init__(), or they don’t use classes at all. They rely on composition within another class and on the fact that Python loads a module only once per program, so instances created in a module are effectively singletons when used elsewhere in the program.

There are other alternatives for situations where you may be tempted to use a singleton. And there may be some less common scenarios when the answer is still to create a singleton class.

So, I’m not stating that you absolutely never need to create a singleton class in Python. But in most cases, there are neater and more Pythonic alternatives.

Still, creating a singleton class, as we did earlier in this article, is a useful learning exercise!


This publication is entirely supported by its readers – there are no adverts, no sponsors! But it takes a lot of effort and time to get one of these articles out. If you want to support this publication further, and get exclusive articles, videos, and more goodies, you can become a paid subscriber.

Subscribe now

You can also support this publication by making a one-off contribution of any amount you wish.

Photo by Marek Piwnicki: https://www.pexels.com/photo/born-by-the-stars-17806401/


Code in this article uses Python 3.14

The code images used in this article are created using Snappify. [Affiliate link]

You can also support this publication by making a one-off contribution of any amount you wish.

Support The Python Coding Stack


For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!

Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.

And you can find out more about me at stephengruppetta.com

Further reading related to this article’s topic:


Appendix: Code Blocks

Code Block #1
class Leaderboard:
    def __init__(self):
        self.scores = {}
Code Block #2
class Leaderboard:
    def __init__(self):
        self.scores = {}

    def add_score(self, player, score):
        if player in self.scores:
            self.scores[player] += score
        else:
            self.scores[player] = score

    def get_leaderboard(self):
        return sorted(
            self.scores.items(),
            key=lambda item: item[1],
            reverse=True,
        )

    def display(self):
        for player, score in self.get_leaderboard():
            print(f”{player}: {score}”)

    def reset(self):
        # You may want to add a confirmation step in a real application,
        # or save a backup to a file first
        self.scores.clear()
Code Block #3
# ...

leaderboard = Leaderboard()
leaderboard.add_score(”Stephen”, 10)
leaderboard.add_score(”Kate”, 15)
leaderboard.add_score(”Stephen”, 4)
leaderboard.display()
Code Block #4
# ...
# later in the game, or in another game instance...
print(”\nNow Dealing With ‘another_leaderboard’”)
another_leaderboard = Leaderboard()
another_leaderboard.add_score(”Max”, 7)
another_leaderboard.add_score(”Sarah”, 13)
another_leaderboard.display()
Code Block #5
# ...
print(id(leaderboard))
print(id(another_leaderboard))
print(leaderboard is another_leaderboard)
Code Block #6
class Leaderboard:
    _instance = None
    
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
      
    # ...
Code Block #7
# ...

leaderboard = Leaderboard()
leaderboard.add_score(”Stephen”, 10)
leaderboard.add_score(”Kate”, 15)
leaderboard.add_score(”Stephen”, 4)
print(”Show leaderboard:”)
leaderboard.display()

# later in the game, or in another game instance...
another_leaderboard = Leaderboard()
another_leaderboard.add_score(”Max”, 7)
another_leaderboard.add_score(”Sarah”, 13)
print(”\nShow leaderboard:”)
another_leaderboard.display()

print()
print(id(leaderboard))
print(id(another_leaderboard))
print(leaderboard is another_leaderboard)
Code Block #8
class Leaderboard:
    # ...
    
    def __init__(self):
        self.scores = {}
		
    # ...
Code Block #9
class Leaderboard:
    # ...

    def __init__(self):
        # Prevent reinitialisation
        if not hasattr(self, “initialised”):
            self.scores = {}
            self.initialised = True

    # ...
Code Block #10
# leaderboard.py

class Leaderboard:
    def __init__(self):
        self.scores = {}

    def add_score(self, player, score):
        if player in self.scores:
            self.scores[player] += score
        else:
            self.scores[player] = score

    def get_leaderboard(self):
        return sorted(
            self.scores.items(),
            key=lambda item: item[1],
            reverse=True,
        )

    def display(self):
        for player, score in self.get_leaderboard():
            print(f”{player}: {score}”)

    def reset(self):
        # You may want to add a confirmation step in a real application,
        # or save a backup to a file first
        self.scores.clear()
        

# Create a single instance of Leaderboard
leaderboard = Leaderboard()
Code Block #11
# main.py

from leaderboard import leaderboard

leaderboard.add_score(”Stephen”, 10)
leaderboard.add_score(”Kate”, 15)
leaderboard.add_score(”Stephen”, 4)
print(”Show leaderboard:”)
leaderboard.display()

# later in the game, or in another game instance...
leaderboard.add_score(”Max”, 7)
leaderboard.add_score(”Sarah”, 13)
print(”\nShow leaderboard:”)
leaderboard.display()
Code Block #12
# leaderboard.py

print(”leaderboard.py loaded”)

class Leaderboard:
    # ...
Code Block #13
# main.py

from leaderboard import leaderboard

leaderboard.add_score(”Stephen”, 10)
leaderboard.add_score(”Kate”, 15)
leaderboard.add_score(”Stephen”, 4)
print(”Show leaderboard:”)
leaderboard.display()

# Note, we usually never place imports in the middle of a file,
# but this is just to illustrate that the singleton instance
# is shared even if we import it again.
from leaderboard import leaderboard

# later in the game, or in another game instance...
leaderboard.add_score(”Max”, 7)
leaderboard.add_score(”Sarah”, 13)
print(”\nShow leaderboard:”)
leaderboard.display()
Code Block #14
_scores = {}

def add_score(player, score):
    if player in _scores:
        _scores[player] += score
    else:
        _scores[player] = score

def get_leaderboard():
    return sorted(
        _scores.items(),
        key=lambda item: item[1],
        reverse=True,
    )

def display_leaderboard():
    for player, score in get_leaderboard():
        print(f”{player}: {score}”)

def reset_leaderboard():
    # You may want to add a confirmation step in a real application,
    # or save a backup to a file first
    _scores.clear()

add_score(”Stephen”, 10)
add_score(”Kate”, 15)
add_score(”Stephen”, 4)
print(”Show leaderboard:”)
display_leaderboard()

# later in the game, or in another game instance...
add_score(”Max”, 7)
add_score(”Sarah”, 13)
print(”\nShow leaderboard:”)
display_leaderboard()
Code Block #15
# game_services.py

from leaderboard import Leaderboard

class GameServices:
    def __init__(self):
        self.leaderboard = Leaderboard()

game_services = GameServices()
Code Block #16
# main.py

from game_services import game_services

game_services.leaderboard.add_score(”Stephen”, 10)
game_services.leaderboard.add_score(”Kate”, 15)
game_services.leaderboard.add_score(”Stephen”, 4)
print(”Show leaderboard:”)
game_services.leaderboard.display()

# later in the game, or in another game instance...
game_services.leaderboard.add_score(”Max”, 7)
game_services.leaderboard.add_score(”Sarah”, 13)
print(”\nShow leaderboard:”)
game_services.leaderboard.display()

For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!

Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.

And you can find out more about me at stephengruppetta.com

October 14, 2025 10:04 PM UTC


PyCoder’s Weekly

Issue #704: Python 3.14 Released and More (Oct. 14, 2025)

#704 – OCTOBER 14, 2025
View in Browser »

The PyCoder’s Weekly Logo


Python 3.14: Exploring the New Features

Python 3.14 is here! Christopher Trudeau returns to discuss the new version with Real Python team member Bartosz Zaczyński. This year, Bartosz coordinated the series of preview articles with members of the Real Python team and wrote the showcase tutorial, “Python 3.14: Cool New Features for You to Try.” Christopher’s video course, “What’s New in Python 3.14”, covers the topics from the article and shows the new features in action.
REAL PYTHON podcast

Python 3.14: Cool New Features for You to Try

Learn what’s new in Python 3.14, including an upgraded REPL, template strings, lazy annotations, and subinterpreters, with examples to try in your code. Also available as a video course.
REAL PYTHON

Quiz: Python 3.14: Cool New Features for You to Try

REAL PYTHON

Python 3.14 Is Here. How Fast Is It?

A comprehensive deep dive comparison on performance figures between Python versions and variations, including the JIT and free-threaded mode.
MIGUEL GRINBERG

Free-Threaded Python Library Compatibility Checker

A heat map and table summarizing Python free-threaded compatibility in a variety of common Python packages.
FT-CHECKER.COM

Python 3.14: 3 asyncio Changes

asyncio changes are often overlooked, in the latest 3.14 release there are 3 new asyncio features and changes.
JAMIE CHANG • Shared by Jamie Chang

Python 3.14.0 Release Announcement

CPYTHON DEV BLOG

Python 3.13.8 Released

CPYTHON DEV BLOG

Python Jobs

Senior Python Developer (Houston, TX, USA)

Technoidentity

More Python Jobs >>>

Articles & Tutorials

Why It Took 4 Years to Get a Lock Files Specification

Lock files have always been tool specific, ranging from the simple format found in requirements.txt to more complex mechanisms in Poetry and uv. In an attempt to normalize the format for interchange, there is a new PEP. This article talks about why it took so long to get there. See also the associated HN Discussion
BRETT CANNON

The Complete PySpark SQL Guide

PySpark handles large datasets but its syntax has a steep learning curve. PySpark SQL solves this by enabling familiar SQL-style DataFrame operations. This walkthrough teaches you everything from loading data to window functions and pandas UDFs.
CODECUT.AI • Shared by Khuyen Tran

Functions: A Complete Reference

This article is a complete reference for all things related to functions in Python, including structure, signatures, *args, **kwargs, using functions as objects, and more.
RODRIGO GIRÃO SERRÃO

“Open Infra Is Not Free”, PyPI Security, & Grants Pause

“The Python Software Foundation has joined the OpenSSF and other organizations in signing the ‘Open Infrastructure Is Not Free: A Joint Statement on Sustainable Stewarding’.”
PSF

Alberta’s Pipelines

Canada’s province of Alberta is oil rich and hosts many pipelines. The province publishes lots of open data on the infrastructure. This post shows you how to explore it.
MARK LITWINTSCHIK

Lazy Imports Using wrapt

PEP 810 proposes adding explicit lazy imports to Python, but you can already achieve this with third party libraries. This post shows you how using wrapt.
GRAHAM DUMPLETON

Pyx: The Other Side of the uv Coin

Talk Python interviews Charlie Marsh and they talk about Astral’s new product, pyx, a service that intends to make package management faster.
KENNEDY & MARSH podcast

pytest 2.6.0 Release

A new version of pytest is out and this quick post shows you the key new feature: changes to how check.raises() works.
PYTHONTEST

Django: One ORM to Rule All Databases

Comparing the Django ORM support across official database backends, so you don’t have to learn it the hard way.
PAOLO MELCHIORRE

Mastering Python Context Managers

Go beyond just using open() and learn how Python context managers work and where they are useful.
MOH HAZIANE

= Alignment in String Formatting

A quick TIL article on how to use = in string formatting to do alignment with numbers.
ROD

Projects & Code

playwright-stealth: Port of Bot Avoiders for Playwright

PYPI.ORG

uv-ship: A CLI-tool for Shipping With uv

GITHUB.COM/FLORATHS

cliprec: Clipboard Monitoring and Recording Tool

GITHUB.COM/ASWEIGART

ascii-moon-phase-python: CLI ASCII Art of Moon Phase

GITHUB.COM/ASWEIGART

Next.js Dev Experience in Python With FastAPI and HTMX

VOLFPETER.GITHUB.IO • Shared by Peter Volf

Events

Weekly Real Python Office Hours Q&A (Virtual)

October 15, 2025
REALPYTHON.COM

Workshop: Creating Python Communities

October 15 to October 16, 2025
PYTHON-GM.ORG

Swiss Python Summit 2025

October 16 to October 18, 2025
PYTHON-SUMMIT.CH

PyCon NL 2025

October 16 to October 17, 2025
PYCON-NL.ORG

PyData Bristol Meetup

October 16, 2025
MEETUP.COM

PyLadies Dublin

October 16, 2025
PYLADIES.COM

PyCon Thailand 2025

October 17 to October 19, 2025
PYCON.ORG

PyCon Finland 2025

October 17 to October 18, 2025
PLONECONF.ORG

PyConES 2025

October 17 to October 20, 2025
PYCON.ORG

PyBay 2025

October 18 to October 19, 2025
PYBAY.ORG

Python Brasil 2025

October 21 to October 28, 2025
PYTHONBRASIL.ORG.BR

PyCon Davao 2025

October 25 to October 27, 2025
DURIANPY.ORG


Happy Pythoning!
This was PyCoder’s Weekly Issue #704.
View in Browser »

alt

[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]

October 14, 2025 07:30 PM UTC


Real Python

Python Descriptors

Descriptors are a specific Python feature that power a lot of the magic hidden under the language’s hood. If you’ve ever thought that Python descriptors are an advanced topic with few practical applications, then this video course is the perfect tool to help you understand this powerful feature. You’ll come to understand why Python descriptors are such an interesting topic and discover the kinds of use cases where you can apply them.

By the end of this video course, you’ll know:


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

October 14, 2025 02:00 PM UTC

October 13, 2025


Talk Python Blog

Talk Python in Production Story

If you were a website that started from modest beginnings and grew over ten years to support a ton of features and a variety of users, wouldn’t it be great if someone told your story?

My 15 minutes in the spotlight are here! Michael just published his book telling the story in Talk Python in Production. I’ll let Michael tell you all about it. Check out his post giving you the full back story on why the book was written and how it’s different than most DevOps books for Python devs.

October 13, 2025 08:59 PM UTC


Jacob Perkins

Monitoring Celery Tasks with Sentry

Sentry is a great tool for monitoring celery tasks, and alerting when they fail or don’t run on time. But it requires a bit of work to setup properly. Below is some sample code for setting up sentry monitoring of periodic tasks, followed by an explanation.

import math
import sentry_sdk
from celery import signals
from sentry_sdk import monitor
from sentry_sdk.integrations.celery import CeleryIntegration

@signals.beat_init.connect # if you use beats
@signals.celeryd_init.connect
def init_sentry(**kwargs):
    sentry_sdk.init(
        dsn=...,
        integrations=[
            CeleryIntegration(monitor_beat_tasks=False)
        ]
    )

@signals.worker_shutdown.connect
@signals.task_postrun.connect
def flush_sentry(**kwargs):
    sentry_sdk.flush(timeout=5)

def add_periodic_task(celery, schedule, task):
    max_runtime = math.ceil(schedule * 4 / 60)
    monitor_config = {
        "recovery_threshold": 1,
        "failure_issue_threshold": 10,
        "checkin_margin": max_runtime,
        "max_runtime": max_runtime,
        "schedule": {
            "type": "interval",
            "value": math.ceil(schedule / 60.0)
            "unit": "minute"
        }
    }
    name = task.__name__
    task = monitor(monitor_slug=name, monitor_config=monitor_config)(task)
    celery.add_periodic_task(schedule, celery.task(task).s(), name=name)

Initialize Sentry

The init_sentry function must be called before any tasks start executing. The sentry docs for celery recommend using the celeryd_init signal. And if you use celery beats for periodic task execution, then you also need to initialize on the beat_init signal.

Monitoring Beats Tasks

In this example, I’m setting monitor_beat_tasks=False to show how you can do manual monitoring. monitor_beat_tasks=True is much simpler, and doesn’t require any code like in add_periodic_task. But in my experience, it’s not reliable when using async celery functions. The automatic beats monitoring uses some celery signals that likely don’t get executed correctly under async conditions. But manual monitoring isn’t that hard with a function wrapper, as shown above.

Adding a Periodic Task

The add_periodic_task function takes a Celery instance, a periodic interval in seconds, and a function to execute. This function can be normal or async. It then does the following:

  1. Calculates a max_runtime in minutes, so that sentry knows when a task has gone over time. This is also used for checkin_margin, giving the task plenty of buffer time before an issue is created. You should adjust these according to your needs.
  2. Creates a monitor_config for sentry, specifying the following:
    • schedule in minutes (rounded up, because sentry doesn’t handle schedules in seconds)
    • the number of failures allowed before creating an issue (I put 10, but you should adjust as needed)
    • how many successful checkins are required before the issue is marked as resolved (1 is the default, but adjust as needed)
  3. Wraps the function in the sentry monitor decorator, using the function’s name as the monitor_slug. With default beats monitoring, the slug is set to the full package.module.function path, which can be quite long and becomes hard to scan when you have many tasks.
  4. Schedules the task in celery.

Sentry Flush

While this may not be strictly necessary, calling sentry_sdk.flush on the worker_shutdown and task_postrun signals ensures that events are sent to sentry when a celery task completes.

Monitoring your crons

Once this is all setup and running, you should be able to go to Insights > Crons in your sentry web UI, and see all your celery tasks. Double check your monitor settings to make sure they’re correct, then sit back and relax, while sentry keeps track of how your tasks are running.

October 13, 2025 05:00 PM UTC


Real Python

How to Use Python: Your First Steps

If you’re starting fresh with programming and wondering how to use Python, this tutorial will give you the foundation you need to get started with Python step by step. You’ll learn how to install Python, run your first program, and understand the language’s clear syntax.

You’ll also explore variables, loops, functions, classes, and error handling while discovering tools, coding style, and libraries that help you write effective Python code from day one.

By the end of this tutorial, you’ll understand that:

  • You can install Python on Windows, macOS, and Linux using binaries, package managers, or distributions like Anaconda.
  • You can use the Python REPL to experiment interactively before writing full scripts, modules, and projects.
  • Built-in data types like strings, lists, dictionaries, and sets provide powerful ways to structure information.
  • You can handle errors with syntax checks, exceptions, and debugging practices to keep your code running smoothly.
  • Tools like editors, integrated development environments (IDEs), and AI assistants can boost your productivity when writing Python code.

You’ll go through your first steps with clear examples so that you can start coding in Python confidently and build on solid ground.

Get Your Code: Click here to download the free sample code that shows you how to use Python.

Take the Quiz: Test your knowledge with our interactive “How to Use Python: Your First Steps” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

How to Use Python: Your First Steps

Review the basics of Python with this quiz. Practice syntax, keywords, variables, errors, and tools every beginner should know.

Why Should You Use Python?

The Python Logo. The Python logo is a trademark of the Python Software Foundation.

Python is a high-level, interpreted, interactive, and object-oriented programming language that’s a great choice as a first language because its code reads like English. It’s flexible, powerful, and allows you to do many things, both big and small.

With Python, you can write basic programs and scripts, as well as create complex and large-scale enterprise solutions. Here’s a sampling of its uses:

Note: To learn more about the tasks you can perform with Python, check out the What Can I Do With Python? tutorial.

You’ll find Python across many high-traffic websites. For example, Reddit is written in Python. Dropbox’s earliest prototypes were in Python, and it remains central there. YouTube uses Python among its back-end languages. Meanwhile, Instagram runs on Django, and Pinterest has historically used Python with a modified Django stack.

Python offers many features that make it attractive as your first programming language:

  • Readable, beginner-friendly syntax: Python’s design favors code readability, so you spend more time learning programming ideas and less time fighting syntax.
  • Accessible: People of all ages, from school children to retirees, have learned Python, and so can you.
  • Batteries included: The standard library ships with modules for file processing, networking, mathematics, date and time processing, testing, and more.
  • Large community and abundant resources: There’s a vast ecosystem of tutorials, videos, forums, and local meetups for every topic and skill level.
  • Proven in the real world: From startups to enterprises and research labs, Python powers production systems, data pipelines, and AI tooling across industries.
  • Versatile and scalable: It can be used for quick scripts and automation, as well as web applications, data analysis, machine learning, and even game development.
  • Free and cross-platform: Python runs on Windows, macOS, and Linux, and it’s free for both personal and commercial use.
  • Open source: Python source code is publicly available under the Python Software Foundation License Version 2, which grants broad rights to use, modify, and distribute, including in proprietary software. Additionally, anyone can contribute to its development.

Compared to other programming languages, Python offers several key features:

  • Interpreted: It’s portable and quicker to experiment with than compiled languages.
  • Multiparadigm: It lets you write code in different styles, including object-oriented, imperative, and functional.
  • Dynamically typed: It checks variable types at runtime, so you don’t need to declare them explicitly.
  • Strongly typed: It won’t let unsafe operations on incompatible types go unnoticed.

There’s a lot more to learn about Python. But by now, you should have a better idea of why Python is so popular and why you should consider learning to program with it.

How Do You Install and Run Python?

Before you can learn how to use Python, you need to install it. Python works on Linux, macOS, Windows, and several other platforms. You can download and install the latest version from the official download page. You also have the option to install and use different Python versions for different projects.

Note: For a complete guide on installing Python on your computer, check out the How to Install Python on Your System: A Guide tutorial.

To check what Python version has been installed globally on your operating system, open the terminal or command line and run the following:

Windows PowerShell
PS> py -V
Shell
$ python3 -V

Read the full article at https://realpython.com/python-first-steps/ »


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

October 13, 2025 02:00 PM UTC

Quiz: How to Use Python: Your First Steps

Ready to check your foundation in Python? This quiz reviews core beginner topics like variables, keywords, strings, and errors. You’ll also practice commands and tools that help you get started quickly.

As you work through the questions, focus on recognizing patterns and understanding why an answer is correct. For a guided introduction, see How to Use Python: Your First Steps.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

October 13, 2025 12:00 PM UTC


Brian Okken

Python lazy imports you can use today

There’s a proposal, PEP 810 – Explicit lazy imports for Python to natively support lazy importing starting in Python 3.15.

However, it has not been accepted yet, and even if it is accepted, 3.15 is a year away. What do we do now?

The techniques covered in this post that allow you to use lazy importing NOW with Python 3.13, Python 3.12, …, really every version of Python.

Let’s look at a small code example that imports something.

October 13, 2025 11:00 AM UTC


Talk Python to Me

#523: Pyrefly: Fast, IDE-friendly typing for Python

Python typing got fast enough to feel invisible. Pyrefly is a new, open source type checker and IDE language server from Meta, written in Rust, with a focus on instant feedback and real-world DX. Today, we will dig into what it is, why it exists, and how it plays with the rest of the typing ecosystem. We have Abby Mitchell, Danny Yang, and Kyle Into from Pyrefly here to dive into the project.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/sentry'>Sentry Error Monitoring, Code TALKPYTHON</a><br> <a href='https://talkpython.fm/agntcy'>Agntcy</a><br> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br/> <br/> <h2 class="links-heading mb-4">Links from the show</h2> <div><strong>Abby Mitchell</strong>: <a href="https://www.linkedin.com/in/abby-mitchell/?featured_on=talkpython" target="_blank" >linkedin.com</a><br/> <strong>Danny Yang</strong>: <a href="https://www.linkedin.com/in/yangdanny97/?featured_on=talkpython" target="_blank" >linkedin.com</a><br/> <strong>Kyle Into</strong>: <a href="https://www.linkedin.com/in/kyleinto/?featured_on=talkpython" target="_blank" >linkedin.com</a><br/> <br/> <strong>Pyrefly</strong>: <a href="https://pyrefly.org?featured_on=talkpython" target="_blank" >pyrefly.org</a><br/> <strong>Pyrefly Documentation</strong>: <a href="https://pyrefly.org/en/docs/?featured_on=talkpython" target="_blank" >pyrefly.org</a><br/> <strong>Pyrefly Installation Guide</strong>: <a href="https://pyrefly.org/en/docs/installation/?featured_on=talkpython" target="_blank" >pyrefly.org</a><br/> <strong>Pyrefly IDE Guide</strong>: <a href="https://pyrefly.org/en/docs/IDE/?featured_on=talkpython" target="_blank" >pyrefly.org</a><br/> <strong>Pyrefly GitHub Repository</strong>: <a href="https://github.com/facebook/pyrefly?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Pyrefly VS Code Extension</strong>: <a href="https://marketplace.visualstudio.com/items?itemName=meta.pyrefly&featured_on=talkpython" target="_blank" >marketplace.visualstudio.com</a><br/> <strong>Introducing Pyrefly: A New Type Checker and IDE Experience for Python</strong>: <a href="https://engineering.fb.com/2025/05/15/developer-tools/introducing-pyrefly-a-new-type-checker-and-ide-experience-for-python/?featured_on=talkpython" target="_blank" >engineering.fb.com</a><br/> <strong>Pyrefly on PyPI</strong>: <a href="https://pypi.org/project/pyrefly/?featured_on=talkpython" target="_blank" >pypi.org</a><br/> <strong>InfoQ Coverage: Meta Pyrefly Python Typechecker</strong>: <a href="https://infoq.com/news/2025/05/meta-pyrefly-python-typechecker/?featured_on=talkpython" target="_blank" >infoq.com</a><br/> <strong>Pyrefly Discord Invite</strong>: <a href="https://discord.gg/Cf7mFQtW7W?featured_on=talkpython" target="_blank" >discord.gg</a><br/> <strong>Python Typing Conformance (GitHub)</strong>: <a href="https://github.com/python/typing/tree/main/conformance?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Typing Conformance Leaderboard (HTML Preview)</strong>: <a href="https://htmlpreview.github.io/?https://github.com/python/typing/blob/main/conformance/results/results.html&featured_on=talkpython" target="_blank" >htmlpreview.github.io</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=P4RKxl_giH4" target="_blank" >youtube.com</a><br/> <strong>Episode #523 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/523/pyrefly-fast-ide-friendly-typing-for-python#takeaways-anchor" target="_blank" >talkpython.fm/523</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/523/pyrefly-fast-ide-friendly-typing-for-python" target="_blank" >talkpython.fm</a><br/> <br/> <strong>Theme Song: Developer Rap</strong><br/> <strong>🥁 Served in a Flask 🎸</strong>: <a href="https://talkpython.fm/flasksong" target="_blank" >talkpython.fm/flasksong</a><br/> <br/> <strong>---== Don't be a stranger ==---</strong><br/> <strong>YouTube</strong>: <a href="https://talkpython.fm/youtube" target="_blank" ><i class="fa-brands fa-youtube"></i> youtube.com/@talkpython</a><br/> <br/> <strong>Bluesky</strong>: <a href="https://bsky.app/profile/talkpython.fm" target="_blank" >@talkpython.fm</a><br/> <strong>Mastodon</strong>: <a href="https://fosstodon.org/web/@talkpython" target="_blank" ><i class="fa-brands fa-mastodon"></i> @talkpython@fosstodon.org</a><br/> <strong>X.com</strong>: <a href="https://x.com/talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @talkpython</a><br/> <br/> <strong>Michael on Bluesky</strong>: <a href="https://bsky.app/profile/mkennedy.codes?featured_on=talkpython" target="_blank" >@mkennedy.codes</a><br/> <strong>Michael on Mastodon</strong>: <a href="https://fosstodon.org/web/@mkennedy" target="_blank" ><i class="fa-brands fa-mastodon"></i> @mkennedy@fosstodon.org</a><br/> <strong>Michael on X.com</strong>: <a href="https://x.com/mkennedy?featured_on=talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @mkennedy</a><br/></div>

October 13, 2025 08:00 AM UTC

October 12, 2025


Anwesha Das

ssh version output in stderr

Generally Linux commands print their version on stdout, for example
git --version or python --version. But not ssh. ssh -V prints output to stderr.

To test it you can do the following:

git version on stdout

> git --version 2> error 1> output 
> cat output
git version 2.51.0

ssh version on stderr

> ssh -V 2>> error 1>> output
> cat error
OpenSSH_9.9p1, OpenSSL 3.2.4 11 Feb 2025

Hope this will be helpful.

October 12, 2025 09:01 PM UTC

October 11, 2025


Paolo Melchiorre

My Django On The Med 2025 🏖️

A summary of my experience at Django On The Med 2025 told through the posts I published on Mastodon during the conference.

October 11, 2025 10:00 PM UTC


Hugo van Kemenade

Releasing Python 3.14.0

Prologue #

I livetooted the release of Python 3.14.0. Here it is in blogpost form!


One week #

Only one week left until the release of Python 3.14.0 final!

What are you looking forward to?

#Python #Python314
Tue, Sep 30, 2025, 15:19 EEST

Three days #

Three days until release and a bug in the Linux kernel has turned a dozen buildbots red…

It’s already been fixed in the kernel, but will take some time to bubble up. We’ll skip that test for relevant kernel versions in the meantime.

Python Release Status Dashboard: 3.15-3.13 are red and ‘✘ Unreleasable: Tier-1 build failed’

3.14 Tier-1 build failed (5), Tier-2 build failed (2), Tier-3 build failed (6), Disconnected Tier-1 builder (with recent build) (1), Disconnected Tier-2 builder (1), Disconnected Tier-3 builder (1), Disconnected Tierless builder (1), Warnings from Tier-1 build (1), Warnings from Tier-2 build (3), Warnings from Tier-3 build (2), Unstable build failed (18), Warnings from unstable build (4), Disconnected unstable builder (30), No problem detected (72)

ERROR: test_aead_aes_gcm (test.test_socket.LinuxKernelCryptoAPI.test_aead_aes_gcm)

#Python #Python314
Sat, Oct 4, 2025, 16:15 EEST

Green #

And back to green!

Python Release Status Dashboard: 3.15 is red: ✘ Unreleasable Tier-1 build failed. 3.14 is green: ✔ Releasable Disconnected Tier-2 builder. 3.13 is orange: ⚠ Concern Tierless build failed

#Python #Python314
Sun, Oct 5, 2025, 16:58 EEST

Release day! #

First off, check blockers and buildbots.

A new release-blocker appeared yesterday (because of course) but it can wait until 3.14.1.

Three deferred-blockers are also waiting until 3.14.1.

A new tier-2 buildbot failure appeared yesterday (because of course) but it had previously been offline for a month and will need some reconfiguration. Can ignore.

OK, let’s make a Python!

#Python #Python314 #release
Tue, Oct 7, 2025, 11:40 EEST

run_release.py #

Next up, merge and backport the final change to What’s New in Python 3.14 to declare it latest stable.

Now start run_release.py, the main release automation script, which does a bunch of pre-checks, runs blurb to create a merged changelog, bumps some numbers, and pushes a branch and tag to my fork. It’ll go upstream at the end of a successful build.

Then kick off the CI to build source zips, docs and Android binaries.

A GitHub Actions build matrix showing an initial verify-input followed by parallel build-source (itself followed by test-source), build-docs, and build-android (consisting of aarch64 and x86_64 jobs).

#Python #Python314 #release
Tue, Oct 7, 2025, 12:43 EEST

Installers #

(That’s actually the second CI attempt, we had to update some script arguments following an Android test runner update.)

This build takes about half an hour.

I’ve also informed the Windows and macOS release managers about the tag and they will start up installer builds.

This takes a few hours, so I’ve got time to finish up the release notes.

PEP 101 is the full process, but much is automated and we don’t need to follow it all manually.

#Python #Python314 #release
Tue, Oct 7, 2025, 12:52 EEST

Windows #

The Windows build has been started.

The jobs with profile-guided optimisation (PGO) build once, then collect a profile by running the tests, and then build again using that profile, to see how ‘real’ code executes and optimises for that.

The Windows build on Azure Pipelines. Lots of boxes for each of ‘build binaries’, ‘sign binaries’, ‘generate layouts’, ‘pack’, ’test’ and finally ‘publish’. So far nearing the end of the build binaries stage.

Meanwhile, the docs+source+Android build has finished and the artifacts have been copied to where they need to go with SBOMs created.

#Python #Python314 #release
Tue, Oct 7, 2025, 13:50 EEST

macOS #

The Windows build is ready and macOS is underway.

Terminal prompt showing the output of run_release.py with lots of checked tasks and ending with: Waiting for files: Linux ✅ Windows ✅ Mac ❌

#Python #Python314 #release
Tue, Oct 7, 2025, 15:36 EEST

Final steps #

macOS installer done, next on to the final publishing and announcing steps.

Terminal showing: ✅ Wait until all files are ready. Go to https://www.python.org/admin/downloads/release/add/ and create a new release. Have you already created a new release for 3.14.0? Enter yes or no:

#Python #Python314 #release
Tue, Oct 7, 2025, 17:02 EEST

🚀 It’s out! #

🥧 Please install and enjoy Python 3.14!

Two snakes enjoying a pie with 3.14 on the top and π crimping.

#Python #Python314 #release
Tue, Oct 7, 2025, 17:27 EEST

Finally #

And the last few tasks: announce also on the blog & mailing lists, update the PEP & downloads landing page, fix Discourse post links, unlock the 3.14 branch for the core team to start landing PRs that didn’t need to be in the RC, and eat the pie.

A circular lemon meringue pie in front of a Mac showing the release logo.

A HUGE thanks to @sovtechfund Fellowship for allowing me to dedicate my time on getting this out 🎉

#Python #Python314 #release
Tue, Oct 7, 2025, 19:28 EEST

October 11, 2025 05:04 PM UTC


Django Weblog

2026 DSF Board Nominations

Nominations are open for the elections of the 2026 Django Software Foundation Board of Directors. The Board guides the direction of the marketing, governance and outreach activities of the Django community. We provide funding, resources, and guidance to Django events on a global level.

The Board of Directors consists of seven volunteers who are elected to two-year terms. This is an excellent opportunity to help advance Django. We can’t do it without volunteers, such as yourself. Anyone including current Board members, DSF Members, or the public at large can apply to the Board. It is open to all.

How to apply

If you are interested in helping to support the development of Django we’d enjoy receiving your application for the Board of Directors. Please fill out the 2026 DSF Board Nomination form by 23:59 on October 31, 2025 Anywhere on Earth to be considered.

Submit your nomination for the 2026 Board

If you have any questions about applying, the work, or the process in general please don’t hesitate to reach out on the Django forum or via email to foundation@djangoproject.com.

Thank you for your time, and we look forward to working with you in 2026!


The 2025 DSF Board of Directors.

October 11, 2025 08:31 AM UTC


Brett Cannon

Why it took 4 years to get a lock files specification

(This is the blog post version of my keynote from EuroPython 2025 in Prague, Czechia.)

We now have a lock file format specification. That might not sound like a big deal, but for me it took 4 years of active work to get us that specification. Part education, part therapy, this post is meant to help explain what make creating a lock file difficult and why it took so long to reach this point.

What goes into a lock file

A lock file is meant to record all the dependencies your code needs to work along with how to install those dependencies.

That involves The "how" is source trees, source distributions (aka sdists), and wheels. With all of these forms, the trick is recording the right details in order to know how to install code in any of those three forms. Luckily we already had the direct_url.json specification that just needed translation into TOML for source trees. As for sdists and wheels, it&aposs effectively recording what an index server provides you when you look at a project&aposs release.

The much trickier part is figuring what to install when. For instance, let&aposs consider where your top-level, direct dependencies come from. In pyproject.toml there&aposs project.dependencies for dependencies you always need for your code to run, project.optional-dependencies (aka extras), for when you want to offer your users the option to install additional dependencies, and then there&aposs dependency-groups for dependencies that are not meant for end-users (e.g. listing your test dependencies).

But letting users control what is (not) installed isn&apost the end of things. There&aposs also the specifiers you can add to any of your listed dependencies. They allow you to not only restrict what versions of things you want (i.e. setting a lower-bound and not setting an upper-bound if you can help it), but also when the dependency actually applies (e.g. is it specific to Windows?).

Put that all together and you end up with a graph of dependencies who edges dictate whether a dependency applies on some platform. If you manage to write it all out then you have multi-use lock files which are portable across platforms and whatever options the installing users selects, compared to single-use lock files that have a specific applicability due to only supporting a single platform and set of input dependencies.

Oh, and even getting the complete list of dependencies in either case is an NP-complete problem.

And it make makes things "interesting", I also wanted the file format to be written by software but readable by people, secure by default, fast to install, and allow the locker which write the lock file to be different from the installer that performs the install (and either be written in a language other than Python).

In the end, it all worked out (luckily); you can read the spec for all the nitty-gritty details about pylock.toml or watch the keynote where I go through the spec. But it sure did take a while to get to this point.

Why it took (over) 4 years

I&aposm not sure if this qualifies as the longest single project I have ever taken on for Python (rewriting the import system might still hold that record for me), but it definitely felt the most intense over a prolonged period of time.

The oldest record I have that I was thinking about this problem is a tweet from Feb 2019:

alt

2019

That year there were 106 posts on discuss.python.org about a requirements.txt v2 proposal. It didn&apost come to any specific conclusion that I can recall, but it at least got the conversation started.

2020

The next year, the conversation continued and generated 43 posts. I was personally busy with PEP 621 and the [project] table in pyproject.toml.

2021

In January of 2021 Tzu-Ping Chung, Pradyun Gedam, and myself began researching how other language ecosystems did lock files. It culminated in us writing PEP 665 and posting it in July. That led to 359 posts that year.

The goal of PEP 665 was a very secure lock file which partially achieved that goal by only supporting wheels. With no source trees or sdists to contend with, it meant installation didn&apost involve executing a build back-end which can be slow, be indeterminate, and a security risk simply due to running more code. We wrote the PEP with the idea that any source trees or sdists would be built into wheels out-of-band so you could then lock against those wheels.

2022

In the end, PEP 665 was rejected in January of 2022, generating 106 posts on the subject both before and after the rejection. It turns out enough people had workflows dependent on sdists that they balked at having the added step of building wheels out-of-band. There was also some desire to also lock the build back-end dependencies.

2023

After the failure of PEP 665, I decided to try to tackle the problem again entirely on my own. I didn&apost want to drag other poor souls into this again and I thought that being opinionated might make things a bit easier (compromising to please everyone can lead to bad outcomes when a spec if large and complicated like I knew this would be).

I also knew I was going to need a proof-of-concept. That meant I needed code that could get metadata from an index server, resolve all the dependencies some set of projects needed (at least from a wheel), and at least know what I would install on any given platform. Unfortunately a lot of that didn&apost exist as some library on PyPI, so I had to write a bunch of it myself. Luckily I had already started the journey before with my mousebender project, but that only covered the metadata from an index server. I still needed to be able to read MEtADATA files from a wheel and do the resolution. The former Donald Stufft had taken a stab at and which I picked up and completed, leading to packaging.metadata. I then used resolvelib to create a resolver.

As such there were only 54 posts about lock files that were general discussion. The key outcome there was trying to lock for build back-ends confused people too much, and so I dropped that feature request from my thinking.

2024

Come 2024, I was getting enough pieces together to actually have a proof-of-concept. And then uv came out in February. That complicated things a bit as it did/planned to do things I had planned to help entice people to care about lock files. I also knew I couldn&apost keep up with the folks at Astral as I didn&apost get to work on this full-time as a job (although I did get a lot more time starting in September of 2024).

I also became a parent in April which initially gave me a chunk of time (babies for the first couple of months sleep a lot, so if gives you a bit of time). And so in July I posted the first draft of PEP 751. It was based on pdm.lock (which itself is based on poetry.lock). It covered sdists and wheels and was multi-use, all by recording the projects to install as a set which made installation fast.

But uv&aposs popularity was growing and they had extra needs that PDM and Poetry– the other major participants in the PEP discussions --didn&apost. And do I wrote another draft where I pivoted from a set of projects to a graph of projects. But otherwise the original feature set was all there.

And then Hynek came by with what seemed like an innocuous request about making the version of a listed project optional instead of required (which was done because the version is required in PKG-INFO in sdists and METADATA in wheels).

alt

Unfortunately the back-and-forth on that was enough to cause the Astral folks to want to scale the whole project back all the way to the requirements.txt v2 solution.

alt

While I understood their reasoning and motivation, I would be lying if I said it wasn&apost disappointing. I felt we were extremely close up to that point in reaching an agreement on the PEP, and then having to walk back so much work and features did not exactly make me happy.

This was covered by 974 posts on discuss.python.org.

2025

But to get consensus among uv, Poetry, and PDM, I did a third draft of PEP 751. This went back to the set of projects to install, but was single-use only. I also became extremely stringent with timelines on when people could provide feedback as well as what would be required to add/remove anything. At this point I was fighting burn-out on this subject and my own wife had grown tired of the subject and seeing me feel dejected every time there was a setback. And so I set a deadline of the end of March to get things done, even if I had to drop features to make it happen.

And in February I thought we had reached and agreement on this third draft. But then Frost Ming, the maintainer of PDM, asked why did we drop multi-use lock files when they thought the opposition wasn&apost that strong?

alt

And so, with another 150 posts and some very strict deadlines for feedback, we managed to bring back multi-use lock files and get PEP 751 accepted-- with no changes! -- on March 31.

2 PEPs and 6 years later ...

If you add in some ancillary discussions, the total number of posts on the subject of lock files since 2019 comes to over 1.8K. But as I write this post, less than 7 months since PEP 751 was accepted, PDM has already been updated to allow users to opt into using pylock.toml over pdm.lock (which shows that the lock file format works and meets the needs of at least one of the three key projects I tried to make happy). Uv and pip also have some form of support.

I will say, though, that I think I&aposm done with major packaging projects (work has also had me move on from working on packaging since April, so any time at this point would be my free time, which is scant when you have a toddler). Between pyproject.toml and pylock.toml, I&aposm ready to move on to the next area of Python where I think I could be the most useful.

October 11, 2025 03:46 AM UTC

October 10, 2025


Python Engineering at Microsoft

Python in Visual Studio Code – October 2025 Release

We’re excited to announce that the October 2025 release of the Python extensions for Visual Studio Code are now available!

This release includes the following announcements:

If you’re interested, you can check the full list of improvements in our changelogs for the Python and Pylance extensions.

Python Environments Extension Improvements

The Python Environments extension received several fixes and updates to enhance your Python development experience in VS Code. Highlights include improved performance and reliability when working with conda environments – now lauching code directly without conda run, a smoother environment flow with Python versions now sorted in descending order for easier acces to the latest releases, fixes for crashes when running Python files that use input(), and improvements to false-positive environment configuration warnings.

The extension also now automatically refreshes environment managers when expanding tree nodes, keeping your environment list up to date without any extra steps.

We appreciate the community feedback that helped identify and prioritize these improvements. Please continue to share your thoughts, suggestions and bug reports on the Python Environments GitHub repository as we continue rolling out this extension.

Enhanced Testing Workflow with Copy Test ID

We’ve improved the testing experience by adding a “Copy Test ID” option to the gutter icon context menu for test functions. This feature allows you to quickly copy test identifiers in pytest format directly from the editor gutter, making it easier to run specific tests from the command line or share test references with teammates.

Copy test id

Improved Shell Startup for Python Environment Activation

We have made improvements to shell start up to reduce issues where terminals created by GitHub Copilot weren’t properly activating Python virtual environments. With the new shell startup approach, you’ll get a more reliable environment activation across terminal creation methods while reducing redundant permission prompts.

Additionally, virtual environment prompts such as (.venv) now appear correctly when PowerShell is activated through shell integration, and we have resolved issues with activation in WSL.

To benefit from these improvements, set your python-envs.terminal.autoActivationType to shellStartup in your VS Code settings.

Other Changes and Enhancements

We have also added small enhancements and fixed issues requested by users that should improve your experience working with Python and Jupyter Notebooks in Visual Studio Code. Some notable changes include:

We would also like to extend special thanks to this month’s contributors:

Try out these new improvements by downloading the Python extension and the Jupyter extension from the Marketplace, or install them directly from the extensions view in Visual Studio Code (Ctrl + Shift + X or ⌘ + ⇧ + X). You can learn more about Python support in Visual Studio Code in the documentation. If you run into any problems or have suggestions, please file an issue on the Python VS Code GitHub page.

The post Python in Visual Studio Code – October 2025 Release appeared first on Microsoft for Python Developers Blog.

October 10, 2025 05:55 PM UTC


Peter Bengtsson

In Python, you have to specify the type and not rely on inference

Unlike TypeScript, if you give a variable a default, which has a type, that variable is implied to always have the type of the default. That's not the case with mypy and ty.

October 10, 2025 01:07 PM UTC


Brian Okken

Installing Python 3.14 on Mac or Windows

The easiest way to install Python 3.14 (or 3.13, 3.12, 3.11, 3.10, 3.10, …)

I originally wrote this post in 2022 for Python 3.11.
From 2022 through 2024, I remained of the belief that installing from python.org was the best option for most people.

However, 2025 changed that for me, with uv and uv python supporting the installation of Python versions. It’s a really pleasant and clean way to keep Python versions up to date and install new versions.

October 10, 2025 12:14 PM UTC


Real Python

The Real Python Podcast – Episode #269: Python 3.14: Exploring the New Features

Python 3.14 is here! Christopher Trudeau returns to discuss the new version with Real Python team member Bartosz Zaczyński. This year, Bartosz coordinated the series of preview articles with members of the Real Python team and wrote the showcase tutorial, "Python 3.14: Cool New Features for You to Try." Christopher's video course, "What's New in Python 3.14", covers the topics from the article and shows the new features in action.


[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

October 10, 2025 12:00 PM UTC


Brian Okken

Testing against Python 3.14

Python 3.14 is here.

If you haven’t done so, it’s time to update your projects to test against 3.14.

The following procedure is what I’m following for a handful of projects. Your process of course may be different if you use different tools.

Honestly, I’m partly writing this down so I don’t have to remember it all in a year when 3.15 rolls around.

Installing Python 3.14

Installing with uv

While it’s true that creating a virtual environment with uv venv .venv --python 3.14 will install 3.14 if it isn’t already there, you still gotta run uv self update. So I just usually install it while I’m at it.

October 10, 2025 08:10 AM UTC


Django Weblog

2025 Malcolm Tredinnick Memorial Prize awarded to Tim Schilling

We are pleased to announce that the 2025 Malcolm Tredinnick Memorial Prize has been awarded to Tim Schilling!

Tim embodies the values that define the Django community: generosity, respect, thoughtfulness, and a deep commitment to supporting others. He is a tireless community leader who creates spaces where newcomers thrive ❤️ exactly in the spirit of our prize and Malcolm Tredinnick’s work.

Tim Schilling on stage at DjangoCon US 2025 with a slide about Governance models, Appointments. Rachell stands next to Tim, and we see the audience in the foreground

About Tim

As a co-founder of Djangonaut Space, Tim has encouraged countless people to take their first steps as contributors. With the overall program but also specific initiatives like co-writing sessions, Space Reviewers, Cosmic Contributors. Many community members trace their involvement in Django back to Tim’s encouragement and support.

Beyond Djangonaut Space, Tim serves on the Django Steering Council, is one of the founders of Django Commons, and is an active member of DEFNA, supporting DjangoCon US. He is known for thoughtful feedback, amplifying others’ work, and encouraging people to step forward for leadership roles.

One host and six panelists on stage at DjangoCon US 2025 in Chicago. Host Velda Kiara stands next to the podium. Panelists are seated: Peter Grandstaff, Dawn Wages, Natalia Bidart, Jeff Triplett, Rachell Calhoun, Tim Schilling holding the microphone

Quotes

Here is some of what people said about Tim’s involvement with the community:

Tim exemplifies all the values the Django community is known for. He is incredibly supportive of newcomers, respectful, and generous. Always ready to give constructive feedback and lend a hand where needed, be it through a pull review or the many Django-related forums he participates in, Tim is a natural leader, someone that the community looks up to.

– Felipe Villegas


Every time he spots a chance to help, he doesn't need to think twice. He's a welcoming person not only with newcomers, as in Djangonaut Space, but also with maintainers through Django Commons. Tim is also very creative, finding different ways to contribute. For example, inside the Djangonaut Space community, the "Space Reviewers" team was formed to host a live stream to help people become reviewers by sharing the process and also actually reviewing a ticket that needs some attention. The Django community is much more than blessed to have Tim, who exemplifies dedication, respect, and support for others.

– Raffaella


Tim just has this way of making sure newcomers feel welcome and get the support they need. He doesn't just talk about community building - he actually does the work to make it happen.

– Abe Hanoka


Tim is a thoughtful and caring community leader. He engages with newcomers in a warm and welcoming manner. In his roles as the admin for Djangonaut Space, the admin of Django Commons, and a member of the Steering Council, he strategically identifies the gaps in the community, collaborates with other members to develop an action plan, and follows through with the execution. He's doing some of the hardest work out there. Not only is Tim nurturing newcomers, he's also growing the community by bridging the gap between newcomers and experienced open source contributors. Tim's actions speak louder than words.

– Lilian

Other nominees

Other nominations for this year included:


Malcolm would be very proud of the legacy he has fostered in our community. Each year we receive many nominations, and it is always hard to pick the winner. If your nominee didn’t make it this year, you can always nominate them again next year!

Congratulations Tim on the well-deserved honor!

October 10, 2025 07:46 AM UTC

October 09, 2025


Everyday Superpowers

Why I switched from HTMX to Datastar

In 2022, David Guillot delivered an inspiring DjangoCon Europe talk, showcasing a web app that looked and felt as dynamic as a React app. Yet he and his team had done something bold. They converted it from React to HTMX, cutting their codebase by almost 70% while significantly improving its capabilities.

Since then, teams everywhere have discovered the same thing: turning a single-page app into a multi-page hypermedia app often slashes lines of code by 60% or more while improving both developer and user experience.

I saw similar results when I switched my projects from HTMX to Datastar. It was exciting to reduce my code while building real-time, multi-user applications without needing WebSockets or complex frontend state management.

The pain point that moved the needle

While preparing my FlaskCon 2025 talk, I hit a wall. I was juggling HTMX and AlpineJS to keep pieces of my UI in sync, but they fell out of step. I lost hours debugging why my component wasn’t updating. Neither library communicates with the other. Since they are different libraries created by different developers, you are the one responsible for helping them work together.

Managing the dance to initialize components at various times and orchestrating events was causing me to write more code than I wanted to and spend more time than I could spare to complete tasks.

Knowing that Datastar had the capability of both libraries with a smaller download, I thought I’d give it a try. It handled it without breaking a sweat, and the resulting code was much easier to understand.

I appreciate that there’s less code to download and maintain. Having a library handle all of this in under 11 KB is great for improving page load performance, especially for users on mobile devices. The less you need to download, the better off you are.

But that's just the starting point.

Better API

As I incorporated Datastar into my project at work, I began to appreciate Datastar’s API. It feels significantly lighter than HTMX. I find that I need to add fewer attributes to achieve the desired results.

For example, most interactions with HTMX require you to create an attribute to define the URL to hit, what element to target with the response, and then you might need to add more to customize how HTMX behaves, like this:

<a hx-target="#rebuild-bundle-status-button"
      hx-select="#rebuild-bundle-status-button"
      hx-swap="outerHTML"
      hx-trigger="click"
      hx-get="/rebuild/status-button"></a>

One doesn’t always need all of these, but I find it common to have two or three attributes every time[2]{And then there are the times I need to remember to look up the ancestry chain to see if any attribute changes the way I’m expecting things to work. Those are confusing bugs when they happen!}.

With Datastar, I regularly use just one attribute, like this:

<a data-on-click="@get('/rebuild/status-button')"></a>

This gives me less to think about when I return months later and need to recall how this works.

How to update page elements

The primary difference between HTMX and Datastar is that HTMX is a front-end library that advances the HTML specification. DataStar is a server-side-driven library that aims to create high-performance, web-native, live-updating web applications.

In HTMX, you describe its behavior by adding attributes to the element that triggers the request, even if it updates something far away on the page. That’s powerful, but it means your logic is scattered across multiple layers. Datastar flips that: the server decides what should change, keeping all your update logic in one place.

To cite an example from HTMX’s documentation:

<div>
   <div id="alert"></div>
    <button hx-get="/info" 
            hx-select="#info-details" 
            hx-swap="outerHTML"
            hx-select-oob="#alert">
        Get Info!
    </button>
</div>

When the button is pressed, it sends a GET request to `/info`, replaces the button with the element in the response that has the ID 'info-details', and then retrieves the element in the response with the ID 'alert', replacing the element with the same ID on the page.

This is a lot for that button element to know. To author this code, you need to know what information you’re going to return from the server, which is done outside of editing the HTML. This is when HTMX loses the ”locality of behavior” I like so much.

Datastar, on the other hand, expects the server to define the behavior, and it works better.

To replicate the behavior above, you have options. The first option keeps the HTML similar to above:

<div>
    <div id="alert"></div>
    <button id="info-details"
     data-on-click="@get('/info')">
        Get Info!
    </button>
</div>

In this case, the server can return an HTML string with two root elements that have the same IDs as the elements they’re updating:

<p id="info-details">These are the details you are looking for…</p>
<div id="alert">Alert! This is a test.</div>

I love this option because it’s simple and performant.

Think at the component level

A better option would change the HTML to treat it as a component.

What is this component? It appears to be a way for the user to get more information about a specific item.

What happens when the user clicks the button? It seems like either the information appears or there is no information to appear, and instead we render an error. Either way, the component becomes static.

Maybe we could split the component into each state, first, the placeholder:

<!-- info-component-placeholder.html -->
<div id="info-component">
    <button data-on-click="@get('/product/{{product.id}}/info')">
        Get Info!
    </button>
</div>

Then the server could render the information the user requests…

<!-- info-component-get.html -->
<div id="info-component">
    {% if alert %}<div id="alert">{{ alert }}</div>{% endif %}
    <p>{{product.additional_information}}</p>
</div>

…and Datastar will update the page to reflect the changes.

This particular example is a little wonky, but I hope you get the idea. Thinking at a component level is better as it prevents you from entering an invalid state or losing track of the user's state.

…or more than one component

One of the amazing things from David Guillot's talk is how his app updated the count of favored items even though that element was very far away from the component that changed the count.

David’s team accomplished that by having HTMX trigger a JavaScript event, which in turn triggered the remote component to issue a GET request to update itself with the most up-to-date count.

With Datastar, you can update multiple components at once, even in a synchronous function.

If we have a component that allows someone to add an item to a shopping cart:

<form id="purchase-item"
      data-on-submit="@post('/add-item', {contentType: 'form'})">"
>
  <input type=hidden name="cart-id" value="{{cart.id}}">
  <input type=hidden name="item-id" value="{{item.id}}">
  <fieldset>
    <button data-on-click="$quantity -= 1">-</button>
    <label>Quantity
      <input name=quantity type=number data-bind-quantity value=1>
    </label>
    <button data-on-click="$quantity += 1">+</button>
  </fieldset>
  <button type=submit>Add to cart</button>
  {% if msg %}
    <p class=message>{{msg}}</p>
  {% endif %}
</form>

And another one that shows the current count of items in the cart:

<div id="cart-count">
    <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
        <use href="#shoppingCart">
    </svg>
    {{count}}
</div>

Then a developer can update them both in the same request. This is one way it could look in Django:

from datastar_py.consts import ElementPatchMode
from datastar_py.django import (
    DatastarResponse,
    ServerSentEventGenerator as SSE,
)

def add_item(request):
    # skipping all the important state updates
	return DatastarResponse([
		SSE.patch_elements(
    		render_to_string('purchase-item.html', context=dict(cart=cart, item=item, msg='Item added!'))
		),
		SSE.patch_elements(
    		render_to_string('cart-count.html', context=dict(count=item_count))
		),
	])
Web native

Being a part of the Datastar Discord, I appreciate that Datastar isn't just a helper script. It’s a philosophy about building apps with the web’s own primitives, letting the browser and the server do what they’re already great at.

Where HTMX is trying to push the HTML spec forward, Datastar is more interested in promoting the adoption of web-native features, such as CSS view transitions, Server-Sent Events, and web components, where appropriate.

This has been a massive eye-opener for me, as I’ve long wanted to leverage each of these technologies, and now I’m seeing the benefits.

One of the biggest wins I achieved with Datastar was by refactoring a complicated AlpineJS component and extracting a simple web component that I reused in multiple places[3]{I’ll talk more about this in an upcoming post.}.

I especially appreciate this because there are times when it's best to rely on JavaScript to accomplish a task. But it doesn't mean you have to reach for a tool like React to achieve it. Creating custom HTML elements is a great pattern to accomplish tasks with high locality of behavior and the ability to reuse them across your app.

However, Datastar provides you with even more capabilities.

Real-time updates for multi-user apps

Apps built with collaboration as a first-class feature stand out from the rest, and Datastar is up to the challenge.

To accomplish this, most HTMX developers achieve updates either by "pulling" information from the server by polling every few seconds or by writing custom WebSocket code, which increases complexity.

Datastar uses a simple web technology called Server-Sent Events (SSE) to allow the server to "push" updates to connected clients. When something changes, such as a user adding a comment or a status change, the server can immediately update browsers with minimal additional code.

You can now build live dashboards, admin panels, and collaborative tools without crafting custom JavaScript. Everything flows from the server, through HTML.

Additionally, suppose a client's connection is interrupted. In that case, the browser will automatically attempt to reconnect without requiring additional code, and it can even notify the server, "This is the last event I received." It's wonderful.

Just because you can do it doesn’t mean you should

Being a part of the Datastar community on Discord has helped me appreciate the Datastar vision of making web apps. They aim to have push-based UI updates, reduce complexity, and leverage tools like web components to handle more complex situations locally. It’s common for the community to help newcomers by helping them realize they’re overcomplicating things.

Here are some of the tips I’ve picked up:

- Don’t be afraid to re-render the whole component and send it down the pipe. It’s easier, it probably won’t affect performance too much, you get better compression ratios, and it’s incredibly fast for the browser to parse HTML strings.

- The server is the state of truth and is more powerful than the browser. Let it handle the majority of the state. You probably don’t need the reactive signals as much as you think you do.

- Web components are great for encapsulating logic into a custom element with high locality of behavior. A great example of this is the star field animation in the header of the Datastar website. The `<ds-starfield>` element encapsulates all the code to animate the star field and exposes three attributes to change its internal state. Datastar drives the attributes whenever the range input changes or the mouse moves over the element.

But you can still reach for the stars

But what I’m most excited about are the possibilities that Datastar enables. The community is routinely creating projects that push well beyond the limits experienced by developers using other tools.

The examples page includes a database monitoring demo that leverages Hypermedia to significantly improve the speed and memory footprint of a demo presented at a JavaScript conference.

The one million checkbox experiment was too much for the server it started on. Anders Murphy used Datastar to create one billion checkboxes on an inexpensive server.

But the one that most inspired me was a web app that displayed data from every radar station in the United States. When a blip changed on a radar, the corresponding dot in the UI would change within 100 milliseconds. This means that *over 800,000 points are being updated per second*. Additionally, the user could scrub back in time for up to an hour (with under a 700 millisecond delay). Can you imagine this as a Hypermedia app? This is what Datastar enables.

How it’s working for me today

I’m still in what I consider my discovery phase of Datastar. Replacing the standard HTMX functionality of ajaxing updates to a UI was quick and easy to implement. Now I’m learning and experimenting with different patterns to use Datastar to achieve more and more.

For decades, I’ve been interested in ways I could provide better user experiences with real-time updates, and I love that Datastar enables me to do push-based updates, even in synchronous code.

HTMX filled me with so much joy when I started using it. But I haven’t felt like I lost anything since switching to Datastar. In fact, I feel like I’ve gained so much more.

If you’ve ever felt the joy of using HTMX, I bet you’ll feel the same leap again with Datastar. It’s like discovering what the web was meant to do all along.


Read more...

October 09, 2025 04:37 PM UTC