skip to navigation
skip to content

Planet Python

Last update: May 02, 2025 07:27 PM UTC

May 02, 2025


Eli Bendersky

Notes on implementing Attention

Some notes on implementing attention blocks in pure Python + Numpy. The focus here is on the exact implementation in code, explaining all the shapes throughout the process. The motivation for why attention works is not covered here too deeply - there are plenty of excellent online resources explaining it.

Several papers are mentioned throughout the code; they are:

Basic scaled self-attention

We'll start with the most basic scaled dot product self-attention, working on a single sequence of tokens, without masking.

The input is a 2D array of shape (N, D). N is the length of the sequence (how many tokens it contains) and D is the embedding depth - the length of the embedding vector representing each token [1]. D could be something like 512, or more, depending on the model.

input array N by D

A self-attention module is parameterized with three weight matrices, Wk, Wq and Wv. Some variants also have accompanying bias vectors, but the AIAYN paper doesn't use them, so I'll skip them here. In the general case, the shape of each weight matrix is (D, HS), where HS is some fraction of D. HS stands for "head size" and we'll see what this means soon. This is a diagram of a self-attention module (the diagram assumes N=6, D is some large number and so is HS). In the diagram, @ stands for matrix multiplication (Python/Numpy syntax):

schematic of a single attention head

Here's a basic Numpy implementation of this:

# self_attention the way it happens in the Transformer model. No bias.
# D = model dimension/depth (length of embedding)
# N = input sequence length
# HS = head size
#
# x is the input (N, D), each token in a row.
# Each of W* is a weight matrix of shape (D, HS)
# The result is (N, HS)
def self_attention(x, Wk, Wq, Wv):
    # Each of these is (N, D) @ (D, HS) = (N, HS)
    q = x @ Wq
    k = x @ Wk
    v = x @ Wv

    # kq: (N, N) matrix of dot products between each pair of q and k vectors.
    # The division by sqrt(HS) is the scaling.
    kq = q @ k.T / np.sqrt(k.shape[1])

    # att: (N, N) attention matrix. The rows become the weights that sum
    # to 1 for each output vector.
    att = softmax_lastdim(kq)
    return att @ v  # (N, HS)

The "scaled" part is just dividing kq by the square root of HS, which is done to keep the values of the dot products manageable (otherwise they would grow with the size of the contracted dimension).

The only dependency is a function for calculating Softmax across the last dimension of an input array:

def softmax_lastdim(x):
    """Compute softmax across last dimension of x.

    x is an arbitrary array with at least two dimensions. The returned array has
    the same shape as x, but its elements sum up to 1 across the last dimension.
    """
    # Subtract the max for numerical stability
    ex = np.exp(x - np.max(x, axis=-1, keepdims=True))
    # Divide by sums across last dimension
    return ex / np.sum(ex, axis=-1, keepdims=True)

When the input is 2D, the "last dimension" is the columns. Colloquially, this Softmax function acts on each row of x separately; it applies the Softmax formula to the elements (columns) of the row, ending up with a row of numbers in the range [0,1] that all sum up to 1.

Another note on the dimensions: it's possible for the Wv matrix to have a different second dimension from Wq and Wk. If you look at the diagram, you can see this will work out, since the softmax produces (N, N), and whatever the second dimension of V is, will be the second dimension of the output. The AIAYN paper designates these dimensions as d_k and d_v, but in practice d_k=d_v in all the variants it lists. I found that these dimensions are typically the same in other papers as well. Therefore, for simplicity I just made them all equal to D in this post; if desired, a variant with different d_k and d_v is a fairly trivial modification to this code.

Batched self-attention

In the real world, the input array is unlikely to be 2D because models are trained on batches of input sequences. To leverage the parallelism of modern hardware, whole batches are typically processed in the same operation.

input array (B, N, D)

The batched version of scaled self-attention is very similar to the non-batched one, due to the magic of Numpy matrix multiplication and broadcasts. Now the input shape is (B, N, D), where B is the batch dimension. The W* matrices are still (D, HS); multiplying a (B, N, D) array by (D, HS) performs contraction between the last axis of the first array and the first axis of the second array, resulting in (B, N, HS). Here's the code, with the dimensions annotated for each operation:

# self_attention with inputs that have a batch dimension.
# x has shape (B, N, D)
# Each of W* has shape (D, D)
def self_attention_batched(x, Wk, Wq, Wv):
    q = x @ Wq  # (B, N, HS)
    k = x @ Wk  # (B, N, HS)
    v = x @ Wv  # (B, N, HS)

    kq = q @ k.swapaxes(-2, -1) / np.sqrt(k.shape[-1])  # (B, N, N)

    att = softmax_lastdim(kq)  # (B, N, N)
    return att @ v  # (B, N, HS)

Note that the only difference between this and the non-batched version is the line calculating kq:

  • Since k is no longer 2D, the notion of "transpose" is ambiguous so we explicitly ask to swap the last and the penultimate axis, leaving the first axis (B) intact.
  • When calculating the scaling factor we use k.shape[-1] to select the last dimension of k, instead of k.shape[1] which only selects the last dimension for 2D arrays.

In fact, this function could also calculate the non-batched version! From now on, we'll assume that all inputs are batched, and all operations are implicitly batched. I'm not going to be using the "batched" prefix or suffix on functions any more.

The basic underlying idea of the attention module is to shift around the multi-dimensional representations of tokens in the sequence towards a better representation of the entire sequence. The tokens attend to each other. Specifically, the matrix produced by the Softmax operation is called the attention matrix. It's (N, N); for each token it specifies how much information from every other token in the sequence should be taken into account. For example, a higher number in cell (R, C) means that there's a stronger relation of token at index R in the sequence to the token at index C.

Here's a nice example from the AIAYN paper, showing a word sequence and the weights produced by two attention heads (purple and brown) for a given position in the input sequence:

attention paper screenshot showing learned attention

This shows how the model is learning to resolve what the word "its" refers to in the sentence. Let's take just the purple head as an example. The index of token "its" in the sequence is 8, and the index of "Law" is 1. In the attention matrix for this head, the value at index (8, 1) will be very high (close to 1), with other values in the same row much lower.

While this intuitive explanation isn't critical to understand how attention is implemented, it will become more important when we talk about masked self-attention later on.

Multi-head attention

The attention mechanism we've seen so far has a single set of K, Q and V matrices. This is called one "head" of attention. In today's models, there are typically multiple heads. Each head does its attention job separately, and in the end all these results are concatenated and feed through a linear layer.

In what follows, NH is the number of heads and HS is the head size. Typically, NH times HS would be D; for example, the AIAYN paper mentions several configurations for D=512: NH=8 and HS=64, NH=32 and HS=16, and so on [2]. However, the math works out even if this isn't the case, because the final linear ("projection") layer maps the output back to (N, D).

Assuming the previous diagram showing a self-attention module is a single head with input (N, D) and output (N, HS), this is how multiple heads are combined:

schematic of multiple attention heads

Each of the (NH) heads has its own parameter weights for Q, K and V. Each attention head outputs a (N, HS) matrix; these are concatenated along the last dimension to (N, NH * HS), which is passed through a final linear projection.

Here's a function implementing (batched) multi-head attention; for now, please ignore the code inside do_mask conditions:

# x has shape (B, N, D)
# In what follows:
#   NH = number of heads
#   HS = head size
# Each W*s is a list of NH weight matrices of shape (D, HS).
# Wp is a weight matrix for the final linear projection, of shape (NH * HS, D)
# The result is (B, N, D)
# If do_mask is True, each attention head is masked from attending to future
# tokens.
def multihead_attention_list(x, Wqs, Wks, Wvs, Wp, do_mask=False):
    # Check shapes.
    NH = len(Wks)
    HS = Wks[0].shape[1]
    assert len(Wks) == len(Wqs) == len(Wvs)
    for W in Wqs + Wks + Wvs:
        assert W.shape[1] == HS
    assert Wp.shape[0] == NH * HS

    # List of head outputs
    head_outs = []

    if do_mask:
        # mask is a lower-triangular (N, N) matrix, with zeros above
        # the diagonal and ones on the diagonal and below.
        N = x.shape[1]
        mask = np.tril(np.ones((N, N)))

    for Wk, Wq, Wv in zip(Wks, Wqs, Wvs):
        # Calculate self attention for each head separately
        q = x @ Wq  # (B, N, HS)
        k = x @ Wk  # (B, N, HS)
        v = x @ Wv  # (B, N, HS)

        kq = q @ k.swapaxes(-2, -1) / np.sqrt(k.shape[-1])  # (B, N, N)

        if do_mask:
            # Set the masked positions to -inf, to ensure that a token isn't
            # affected by tokens that come after it in the softmax.
            kq = np.where(mask == 0, -np.inf, kq)

        att = softmax_lastdim(kq)  # (B, N, N)
        head_outs.append(att @ v)  # (B, N, HS)

    # Concatenate the head outputs and apply the final linear projection
    all_heads = np.concatenate(head_outs, axis=-1)  # (B, N, NH * HS)
    return all_heads @ Wp  # (B, N, D)

It is possible to vectorize this code even further; you'll sometimes see the heads laid out in a separate (4th) dimension instead of being a list. See the Vectorizing across the heads dimension section.

Masked (or Causal) self-attention

Attention modules can be used in both encoder and decoder blocks. Encoder blocks are useful for things like language understanding or translation; for these, it makes sense for each token to attend to all the other tokens in the sequence.

However, for generative models this presents a problem: if during training a word attends to future words, the model will just "cheat" and not really learn how to generate the next word from only past words. This is done in a decoder block, and for this we need to add masking to attention.

Conceptually, masking is very simple. Consider the sentence:

People like watching funny cat videos

When our attention code generates the att matrix, it's a square (N, N) matrix with attention weights from each token to each other token in the sequence:

attention masking

What we want is for all the gray cells in this matrix to be zero, to ensure that a token doesn't attend to future tokens. The blue cells in the matrix add up to 1 in each row, after the softmax operation.

Now take a look at the previous code sample and see what happens when do_mask=True:

  1. First, a (N, N) lower-triangular array is prepared with zeros above the diagonal and ones on the diagonal and below.
  2. Then, before we pass the scaled QK^T to softmax, we set its values to -\infty wherever the mask matrix is 0. This ensures that the softmax function will assign zeros to outputs at these indices, while still producing the proper values in the rest of the row.

Another name for masked self-attention is causal self-attention. This is a very good name that comes from causal systems in control theory.

Intuition - what attention does

What does the attention block try to accomplish? To think about it intuitively, let's focus on a single token in the input (ignoring batch) - x[i]. For this token, the attention block produces an output token out[i] that blends x[i]'s embedding (multi-dimensional dense vector representation) with contextual information from all the tokens preceding it in the sequence, i.e. x[:i].

The way this is done is first calculating the query vector q for x[i] (using Wq). This query can be thought of as "what attributes does this token care about in its context tokens".

Then, for each of the context tokens (including x[i] itself) we calculate:

  • Key (using Wk): these are the attributes of the token that queries may refer to.
  • Value (using Wv): these are the associated values tokens carry.

When attention calculates q @ K.T for each token, the result is - for each context token - the weights to use for mixing in the token's value. Then, when this is multiplied by V, the values are properly weighted.

So this is a very general approach for the model to learn what kind of information each token "cares" about in its context tokens, and how to blend the token's embedding with those of the preceding context tokens, to properly encode the context the token is encountered in.

Our implementation, starting with the basic scaled self-attention, implements this for all tokens in the input sequence simultaneously; hence, we don't just take a single x[i], calculate its q and then multiply that by K.T. Rather, we calculate Q from all x, and continue using matrix multiplications to vectorize these calculations across the entire sequence.

It's important to keep in mind that this intuitive explanation suffers from anthropomorphism. We try to explain what the model does intuitively, but in reality this is only a very abstract approximation of what's happening (consider that attention has multiple heads, and also that that LLMs typically have dozens of repeating transformer layers with self-attention blocks, applying the same mechanism over and over again).

Cross-attention

So far we've been working with self-attention blocks, where the self suggests that elements in the input sequence attend to other elements in the same input sequence.

Another variant of attention is cross-attention, where elements of one sequence attend to elements in another sequence. This variant exists in the decoder block of the AIAYN paper. This is a single head of cross-attention:

cross-attention with different Nq, Nv

Here we have two sequences with potentially different lengths: xq and xv. xq is used for the query part of attention, while xv is used for the key and value parts. The rest of the dimensions remain as before. The output of such a block is shaped (Nq, HS).

This is an implementation of multi-head cross-attention; it doesn't include masking, since masking is not typically necessary in cross attention - it's OK for elements of xq to attend to all elements of xv [3]:

# Cross attention between two input sequences that can have different lengths.
# xq has shape (B, Nq, D)
# xv has shape (B, Nv, D)
# In what follows:
#   NH = number of heads
#   HS = head size
# Each W*s is a list of NH weight matrices of shape (D, HS).
# Wp is a weight matrix for the final linear projection, of shape (NH * HS, D)
# The result is (B, Nq, D)
def multihead_cross_attention_list(xq, xv, Wqs, Wks, Wvs, Wp):
    # Check shapes.
    NH = len(Wks)
    HS = Wks[0].shape[1]
    assert len(Wks) == len(Wqs) == len(Wvs)
    for W in Wqs + Wks + Wvs:
        assert W.shape[1] == HS
    assert Wp.shape[0] == NH * HS

    # List of head outputs
    head_outs = []

    for Wk, Wq, Wv in zip(Wks, Wqs, Wvs):
        q = xq @ Wq  # (B, Nq, HS)
        k = xv @ Wk  # (B, Nv, HS)
        v = xv @ Wv  # (B, Nv, HS)

        kq = q @ k.swapaxes(-2, -1) / np.sqrt(k.shape[-1])  # (B, Nq, Nv)

        att = softmax_lastdim(kq)  # (B, Nq, Nv)
        head_outs.append(att @ v)  # (B, Nq, HS)

    # Concatenate the head outputs and apply the final linear projection
    all_heads = np.concatenate(head_outs, axis=-1)  # (B, Nq, NH * HS)
    return all_heads @ Wp  # (B, Nq, D)

Vectorizing across the heads dimension

The multihead_attention_list implementation shown above uses lists of weight matrices as input. While this makes the code clearer, it's not a particularly friendly format for an optimized implementation - especially on accelerators like GPUs and TPUs. We can vectorize it further by creating a new dimension for attention heads.

To understand the trick being used, consider a basic matmul of (8, 6) by (6, 2):

basic matrix multiplication

Now suppose we want to multiply our LHS by another (6, 2) matrix. We can do it all in the same operation by concatenating the two RHS matrices along columns:

concatenated basic matrix multiplication

If the yellow RHS block in both diagrams is identical, the green block of the result will be as well. And the violet block is just the matmul of the LHS by the red block of the RHS. This stems from the semantics of matrix multiplication, and is easy to verify on paper.

Now back to our multi-head attention. Note that we multiply the input x by a whole list of weight matrices - in fact, by three lists (one list for Q, one for K, and another for V). We can use the same vectorization technique by concatenating all these weight matrices into a single one. Assuming that NH * HS = D, the shape of the combined matrix is (D, 3 * D). Here's the vectorized implementation:

# x has shape (B, N, D)
# In what follows:
#   NH = number of heads
#   HS = head size
#   NH * HS = D
# W is expected to have shape (D, 3 * D), with all the weight matrices for
# Qs, Ks, and Vs concatenated along the last dimension, in this order.
# Wp is a weight matrix for the final linear projection, of shape (D, D).
# The result is (B, N, D).
# If do_mask is True, each attention head is masked from attending to future
# tokens.
def multihead_attention_vec(x, W, NH, Wp, do_mask=False):
    B, N, D = x.shape
    assert W.shape == (D, 3 * D)
    qkv = x @ W  # (B, N, 3 * D)
    q, k, v = np.split(qkv, 3, axis=-1)  # (B, N, D) each

    if do_mask:
        # mask is a lower-triangular (N, N) matrix, with zeros above
        # the diagonal and ones on the diagonal and below.
        mask = np.tril(np.ones((N, N)))

    HS = D // NH
    q = q.reshape(B, N, NH, HS).transpose(0, 2, 1, 3)  # (B, NH, N, HS)
    k = k.reshape(B, N, NH, HS).transpose(0, 2, 1, 3)  # (B, NH, N, HS)
    v = v.reshape(B, N, NH, HS).transpose(0, 2, 1, 3)  # (B, NH, N, HS)

    kq = q @ k.swapaxes(-1, -2) / np.sqrt(k.shape[-1])  # (B, NH, N, N)

    if do_mask:
        # Set the masked positions to -inf, to ensure that a token isn't
        # affected by tokens that come after it in the softmax.
        kq = np.where(mask == 0, -np.inf, kq)

    att = softmax_lastdim(kq)  # (B, NH, N, N)
    out = att @ v  # (B, NH, N, HS)
    return out.transpose(0, 2, 1, 3).reshape(B, N, D) @ Wp  # (B, N, D)

This code computes Q, K and V in a single matmul, and then splits them into separate arrays (note that on accelerators these splits and later transposes may be very cheap or even free as they represent a different access pattern into the same data).

Each of Q, K and V is initially (B, N, D), so they are reshaped into a more convenient shape by first splitting the D into (NH, HS), and finally changing the order of dimensions to get (B, NH, N, HS). In this format, both B and NH are considered batch dimensions that are fully parallelizable. The QK^T computation can then proceed as before, and Numpy will automatically perform the matmul over all the batch dimensions.

Sometimes you'll see an alternative notation used in papers for these matrix multiplications: numpy.einsum. For example, in our last code sample the computation of kq could also be written as:

kq = np.einsum("bhqd,bhkd->bhqk", q, k) / np.sqrt(k.shape[-1])

See this post for my detailed notes on this notation.

Code

The full code for these samples, with tests, is available in this repository.


[1]In LLM papers, D is often called d_{model}.
[2]In the GPT-3 paper, this is also true for all model variants. For example, the largest 175B model has NH=96, HS=128 and D=12288.
[3]It's also not as easy to define mathematically: how do we make a non-square matrix triangular? And what does it mean when the lengths of the two inputs are different?

May 02, 2025 08:36 AM UTC


Seth Michael Larson

whichprovides: an abstraction of "yum provides"

This critical role would not be possible without funding from the Alpha-Omega project.

I'm announcing a new small project I've created as a part of my work on Software Bill-of-Materials for Python packages. The library is called whichprovides and it's available on PyPI under the same name:

$ python -m pip install whichprovides

You can use the tool as a CLI, but many users will be using this library indirectly through tools like auditwheel.

The primary purpose of the library is to reverse a file path on your system back to the package ecosystem and package that "provided" the file, similar to how yum provides works:

$ python -m whichprovides /usr/bin/python3.10
/usr/bin/python3.10: pkg:deb/ubuntu/python3.10-minimal@3.10.12-1~22.04.9

This information allows tools to create package URLs (PURLs) for the files they use for building a Python package. PURLs are useful as a software identifier for performing vulnerability scanning.

Currently, this library supports the following package ecosystems:

I'm interested in adding support for other package ecosystems, too. If you'd like to contribute support for a new package ecosystem or just generally review the code, I'd welcome these types of contributions.

May 02, 2025 12:00 AM UTC


Techiediaries - Django

Generate and Crack Passwords with Python and Ethical Considerations

In modern cybersecurity space, password security remains a critical line of defense against unauthorized access. This post explains Python’s capabilities for both generating secure passwords and simulating password-cracking techniques.

May 02, 2025 12:00 AM UTC

Find Wi-Fi Connected Devices with Python

This article provides a step by step approach to building a Python script for identifying devices connected to a Wi-Fi network. By leveraging Address Resolution Protocol (ARP) scanning and MAC address vendor lookup tools, the solution automates device discovery and enhances network visibility. Below, we explore the theory and steps, and optimization hacks for building a robust network monitoring tool.

May 02, 2025 12:00 AM UTC

Finding Geo Location with Python Using IP and GPS

This article explains five distinct methods for implementing geolocation in Python, providing detailed implementations for both IP-based and GPS-based approaches.

May 02, 2025 12:00 AM UTC


Quansight Labs Blog

Enhancing Developer Experience at SciPy - Intel oneAPI/MSVC Support and Migrating to spin

Highlights the work done to improve developer experience at SciPy, specifically on supporting Intel oneAPI/MSVC and spin

May 02, 2025 12:00 AM UTC

May 01, 2025


Zero to Mastery

[April 2025] Python Monthly Newsletter 🐍

65th issue of Andrei Neagoie's must-read monthly Python Newsletter: Python's New t-strings, The Best Programmers I Know, and much more. Read the full newsletter to get up-to-date with everything you need to know from last month.

May 01, 2025 10:00 AM UTC


Seth Michael Larson

Better boosting on Mastodon with smart clients

Happy May Day (aka International Workers' Day). Consider celebrating by reading about the origins of May Day and how workers before us fought and died for the 8-hour work week that we, the workers of today, now enjoy.

If you've been on Mastodon for long enough it's likely you've heard the phrase “you are the algorithm” in reference to the fact that Mastodon by default doesn't provide algorithmic curation of your timeline. Instead, Mastodon implements a simple linear timeline and users are expected to "boost" posts so they reach a wider audience, specifically their own followers. I'll be calling Mastodon's approach "boost curation" in this post.

Don't get me wrong, I'm a fan of understandable systems and a critic of algorithmic curation. I do wonder if Mastodon's allergy to algorithm curation does not play well for folks who have different expectations of social media, such as wanting more "town-square" moments [1] where a substantial number of users are talking about one or two topics that interest them all at once.

Here are some other downsides of boost curation compared to algorithmic curation:

So what can be done about the above issues while maintaining a linear algorithm-less timeline?

Breaking assumptions for smarter boosts

Many other networked systems have methods for clients to "collaborate" and achieve a goal that helps users without actually needing to know intimate details about other implementations (which is impossible in a diverse ecosystem of clients) or to share additional information between clients. An example that comes to mind here is the different TCP congestion control algorithms which collaborate to maximize bandwidth without needing to directly share information.

What if selecting "boost" in a Mastodon client didn't mean the post was reposted immediately to your followers, only that the post would be reposted eventually at the clients' discretion? To keep control in the users' hands, clients could offer an override to repost immediately, like a double-tap?

With this assumption broken, the client is now allowed to be "smart" by choosing the timing when the boost is applied to a post. The following simple logic could be implemented:

The above basic routine could be refined mostly by how the "delay" is calculated. The delay could be a simple whole number or more complicated, like taking into account information about your followers or recent posts on the same topic. To avoid thundering herds between like-minded clients this delay could be treated almost like a "retry" where a random jitter is applied.

Are there any Mastodon clients that already implement something similar to this that I'm not aware of? What pitfalls or other choices could a client make to make this behavior better? Send me your thoughts on this topic or Mastodon in general.

[1] Note that there are downsides to being a social media town-square, such as context collapse.

May 01, 2025 12:00 AM UTC

April 30, 2025


Juri Pakaste

Updating multiple rows with SQL and avoiding collisions

I ran into an interesting problem with SQL the other day: how do you update multiple rows while maintaining an uniqueness constraint?

I have a table where each row describes an item in an ordered list. Each row has a position value. They are integers, not necessarily contiguous but each unique. A larger position means the row is further down the list. For reordering the rows, I sometimes need to make space between two positions.

How do you do that? Well, the obvious answer is a simple update statement, assuming you want to make space for one item before position 5:

UPDATE table
SET position = position + 1
WHERE position >= 5

Despite having used SQL since the 90s I don't think I've ever needed to do this before. It seemed simple enough, but I found out the solution I went with is not only obvious but also wrong. When you have contiguous position values, that statement causes a unique constraint violation, in both SQLite and PostgreSQL. Having transaction isolation doesn't prevent collisions during updates, even if the end state would be valid.

A helpful LLM tried to suggest the broken solution, then a solution that caused a syntax error and then a solution that involved creating a temporary table. After that I went back to searching the web and finally found a mention about negating the values temporarily which sounded like way less hassle than temporary tables.

That worked great. So, to add space for N items before position X:

  1. Begin transaction.
  2. Instead of incrementing the value on each row greater than or equal to X by N, multiply the value by -1 and decrement it by N.
  3. Multiply each value smaller than zero by -1.
  4. Commit.
BEGIN TRANSACTION;

UPDATE table
SET position = -position - 1
WHERE position >= 5;

UPDATE table
SET position = -position
WHERE position < 0;

COMMIT;

April 30, 2025 02:50 PM UTC


Real Python

Modern Web Automation With Python and Selenium

Selenium is a web automation tool that allows you to use Python to programmatically interact with dynamic, JavaScript-generated web pages. Your Python Selenium code drives a real browser that you can instruct to fill out forms, click buttons, scrape dynamically generated data, or write automated tests for web applications.

By implementing the Page Object Model (POM) design pattern, you can create clean and scalable automation scripts that are straightforward to read and maintain.

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

  • Selenium allows you to launch browsers, visit URLs, and interact with web elements.
  • Headless browsers let you run scripts without displaying a browser window, which is useful for automation and testing.
  • You can target web elements using different locators, such as CSS selectors, XPath, or IDs.
  • Explicit waits provide a flexible way to handle dynamic content by waiting for specific conditions.
  • The Page Object Model design pattern separates page structure from business logic.

In this tutorial, you’ll learn how to use Selenium with Python to build a fully functional music player that interacts with Bandcamp’s Discover page. You’ll control the player from the command line while a headless Firefox browser runs in the background. With it, you’ll be able to play tracks, pause music, list available tracks, and load more tracks, replicating some of the website’s core functionality.

Along the way, you’ll learn modern best practices, like implementing the Page Object Model (POM), which helps keep your automation scripts clean, testable, and maintainable. Ready to get started? Head over to bandcamp.com/discover/ and play some of the available music to get a feel for the website and pump up your mood for this project!

Get Your Code: Click here to download the free sample code that shows you how to use Selenium in Python for modern web automation.

Take the Quiz: Test your knowledge with our interactive “Web Automation With Python and Selenium” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Web Automation With Python and Selenium

In this quiz, you'll test your understanding of using Selenium with Python for web automation. You'll revisit concepts like launching browsers, interacting with web elements, handling dynamic content, and implementing the Page Object Model (POM) design pattern.

Understand the Project and Approach

Web automation involves using a script to drive a browser and perform actions such as clicking links, filling out forms, and gathering data. Instead of manually navigating a website, you can delegate these tasks to Python. A typical scenario is automating repetitive tasks, such as logging in daily to a tool or scraping regularly updated data.

Because many web apps are built for human interaction, they can present challenges when you try to interact with them automatically. In the early days of the internet, you could send HTTP requests and parse the resulting HTML. But modern sites often rely on JavaScript to handle events or generate content dynamically, meaning that an HTTP request alone probably won’t reveal the full page content. That’s where Selenium comes in.

The Selenium Project

Selenium is a mature open-source project that provides a convenient API to control browsers. With Selenium, you can:

  • Launch a headless or visible browser such as Firefox or Chrome using a web driver.
  • Visit URLs and navigate pages just like a real user would.
  • Locate elements with CSS selectors, XPath, or similar locators.
  • Interact with elements by clicking, typing, dragging, or waiting for them to change.

Once you install the appropriate driver for your browser, you can control your browser through a script using Selenium.

Selenium itself is written in Java, but has bindings for different programming languages. In Python, it’s distributed on PyPI as a single package called selenium, which you can install using pip.

Selenium is often used for automated testing, but it’s equally useful for generic web automation, which is what this tutorial will focus on.

Note: You might be wondering how Selenium differs from other tools for scripted web interactions, such as Beautiful Soup, Scrapy, or Requests.

One central difference is that those tools are great at handling static data, while Selenium allows you to replicate user behavior at the JavaScript level. This means that you can interact with dynamically generated web content using Selenium.

Before diving into the nuts and bolts of Selenium, it’s helpful to get a clear picture of what you’ll build by the end of this tutorial. As mentioned, you’ll create a fully functional, console-based music player that interacts with the Bandcamp Discover page using a headless Firefox browser.

Your Bandcamp Discover Music Player

Bandcamp is a popular online record store and music community where you can stream songs, explore artists, and discover new albums.

Selenium allows you to automate direct interactions with Bandcamp’s web interface—as though you were clicking and scrolling yourself!

Your finished project will open the Bandcamp Discover page in the background, which means you won’t get to see any of the wonderful album artwork:

A view of the Bandcamp Discover page that shows album artwork, a player, and genre selection buttons on the top

If a browser automation tool creates a browser instance without a visible browser window, it’s said to run in headless mode. But don’t lose your head over that word—your code will stay calm and in control!

Read the full article at https://realpython.com/modern-web-automation-with-python-and-selenium/ »


[ 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 ]

April 30, 2025 02:00 PM UTC

Quiz: Web Automation With Python and Selenium

In this quiz, you’ll test your understanding of Web Automation With Python and Selenium.

By working through this quiz, you’ll revisit concepts like launching browsers, interacting with web elements, handling dynamic content, and implementing the Page Object Model (POM) design pattern.


[ 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 ]

April 30, 2025 12:00 PM UTC


Anwesha Das

PyCon Lithuania, 2025

Each year, I try to experience a new PyCon. 2025, PyCon Lithuania was added to my PyCon calendar.

pyon_lt_6.jpg

Day before the conference

What makes this PyCon, is that we were traveling there as a family and the conference days coincided with the Easter holidays. We utilized that to explore the city—the ancient cathedrals, palaces, old cafes, and of course the Lithuanian cuisine. Šaltibarščiai, Balandeliai and Cepelinai.

Tuesday

22nd, the day before the conference was all about practicing the talk and meeting with the community. We had the pre-conference mingling session with the speakers and volunteers. It was time to meet some old and many new people. Then it was time for PyLadies. Inga from PyLadies Lithuania, Nina from Pyladies London and I had a lovely dinner discussion—good food with the PyLadies community,technology, and us.

pyon_lt_2.jpg

Wednesday

The morning started early for us on the day of the conference. All the 3 of us had different responsibilities during the conference. While Py was volunteering, I talked and Kushal was the morning keynoter A Python family in a true sense :)

pyon_lt_1.jpg

I had my talk, “Using PyPI Trusted Publishing to Ansible Release” scheduled for the afternoon session. The talk was about automating the Ansible Community package release process with GitHub action using the trusted publisher in PyPI. The talk described - what is trusted publishing.I explanined the need for it and the usage of trusted publishing. I explained the Ansible manual release process in a nutshell and then moved to what the Ansible release process is now with  GitHub actions and Trusted Publishing. Then the most important part is, the lessons learned in the process and how other open-source communities can get help and benefit from it.Here is the link for the slides of my talk I had questions regarding trusted publishing, experience as a release manager, and of course Ansible.

pyon_lt_0.jpeg

It was the time to bid goodbye to PyCon Lt and come back home. See you next year. Congratulatios organizers for doing a great job in organizing the coference.

pyon_lt_4.jpg

April 30, 2025 10:49 AM UTC

April 29, 2025


TechBeamers Python

Top Python Code Quality Tools to Improve Your Development Workflow

Are you using tools to improve your Python code quality? If not, you’re missing out! This guide reveals the best code quality tools to write cleaner, faster, and more reliable Python code. Let’s dive in! Writing clean and efficient Python code is crucial for developers. However, maintaining high-quality code can be challenging. Many developers search […]

Source

April 29, 2025 07:50 PM UTC


PyCoder’s Weekly

Issue #679: Regexes, Deep Copy, Enum, and More (April 29, 2025)

#679 – APRIL 29, 2025
View in Browser »

The PyCoder’s Weekly Logo


Regex Affordances

A tour of some real code showing little-used power features of the Python regular expression module, including verbose regex syntax, calling re.sub() with a function reference, and more.
NED BATCHELDER

Shallow vs Deep Copying of Python Objects

What’s the difference between a shallow copy and a deep copy of a Python object? Learn how to clone arbitrary objects in Python, including your own custom classes.
REAL PYTHON

Quiz: Shallow vs Deep Copying of Python Objects

REAL PYTHON

Learn AI In 5 Minutes A Day

alt

Everyone talks about AI, but no one has the time to learn it. So, we found the simplest way to learn AI as quickly as possible: The Rundown AI. It’s the most trusted AI newsletter, with 1M+ readers and exclusives with AI leaders like Mark Zuckerberg, Demis Hassibis, Mustafa Suleyman, and more →
THE RUNDOWN AI sponsor

Module enum Overview

This article gives an overview of the tools available in the module enum and how to use them, including Enum, auto, StrEnum, Flag, and more.
RODRIGO GIRÃO SERRÃO • Shared by Rodrigo Girão Serrão

Call for EuroPython 2026 Host Venues

EUROPYTHON-SOCIETY.ORG

PEP 787: Safer Subprocess Usage Using t-strings (Postponed to 3.15)

PYTHON.ORG

Articles & Tutorials

Getting Started With Python IDLE

In this tutorial, you’ll learn how to use the development environment included with your Python installation. Python IDLE is a small program that packs a big punch! You’ll learn how to use Python IDLE to interact with Python directly, work with Python files, and improve your development workflow.
REAL PYTHON

15k Lines of Verified Cryptography Now in Python

Over the last two years there has been an ongoing project to automatically populate the cryptographic libraries in the Python build, helping to ensure their validity and security. This project is now complete, and has been done without effecting the releases along the way.
JONATHAN PROTZENKO

MySQL Databases and Python

In this video course, you’ll learn how to connect your Python application with a MySQL database. You’ll design a movie rating system and perform some common queries on it. You’ll also see best practices and tips to prevent SQL injection attacks.
REAL PYTHON course

Python’s New t-strings

Using f-strings is a readable way of building output, but there are situations where they can’t be used because the contents need to be verified before being string-ified. The new t-strings, coming in 3.14, are a solution to this problem.
DAVE PECK

How to Run Python in Production

This post is a series of recommendations for running Python in a production environment. It includes package management, linters, preventing secrets being leaked, and much more.
ASHISH BHATIA

Feedback Loops in Python

How fast can we get useful feedback on the Python code we write? Learn different techniques to get quick results to better understand if your code is doing what it is supposed to.
DAVIDVUJIC.BLOGSPOT.COM • Shared by David Vujic

Django Admin Theme Roundup 2025

The Django Admin isn’t the prettiest thing out there but there are third party libraries that improve its appearance. This post talks about some of the most popular ones.
ADAM HILL

Choosing the Right Python Task Queue

Python has great options for task queues. Choosing between Celery and RQ isn’t an easy decision. Jump in and learn how each option compares!
JUDOSCALE.COM • Shared by Jeff Morhous

Django Ledger: Accounting With Python

Talk Python interviews Miguel Sanda about Django Ledger, a library you can use to build your own accounting system.
KENNEDY & SANDA

Frankenstein’s __init__

The untold story of the craziest __init__ Ohad has ever seen.
OHAD RAVID

Projects & Code

mininterface: Interface to Python GUI, TUI, CLI, & Web

GITHUB.COM/CZ-NIC

asv: Benchmarking Tool With Web-Based Reporting

GITHUB.COM/AIRSPEED-VELOCITY

PyFunctional: Data Pipelines With Functional Programming

GITHUB.COM/ENTILZHA

Robyn: Async Python Web Framework With a Rust Runtime

GITHUB.COM/SPARCKLES

msgspec: Fast Serialization and Validation Library

GITHUB.COM/JCRIST

Events

Weekly Real Python Office Hours Q&A (Virtual)

April 30, 2025
REALPYTHON.COM

PyCamp España 2025

May 1 to May 5, 2025
PYCAMP.ES

Canberra Python Meetup

May 1, 2025
MEETUP.COM

Sydney Python User Group (SyPy)

May 1, 2025
SYPY.ORG

Michigan Python May Meeting

May 1 to May 2, 2025
MEETUP.COM


Happy Pythoning!
This was PyCoder’s Weekly Issue #679.
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 ]

April 29, 2025 07:30 PM UTC


Real Python

Thread Safety in Python: Locks and Other Techniques

Threads share state in your programs, which means race conditions can be created when two or more threads fight to update a value. This course is about the various primitives you can use to ensure atomic access to your program’s shared state.

By the end of this video course, you’ll be able to identify safety issues and prevent them by using the synchronization primitives in Python’s threading module to make your code thread-safe.

In this video course, you’ll learn:


[ 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 ]

April 29, 2025 02:00 PM UTC


PyBites

From Backend to Frontend: Connecting FastAPI and Streamlit

In my previous Pybites article, I showed how I built and deployed a book tracking API using FastAPI, Docker, and Fly.io. That project taught me a lot about backend development, containers, and deploying modern APIs.

But a backend alone isn’t enough—users need an interface. And FastAPI’s Swagger UI, while useful for testing, just isn’t user-friendly enough for everyday use.

So, I decided to build a frontend. My goal? A fast, Python-based UI with minimal hassle. That’s when I turned to Streamlit.

In this article, I’ll explain why I used Streamlit and how I was able to connect my deployed backend to this frontend.

This article will be perfect for those wanting to build a full stack Python application, or even for developers wanting to connect their existing backends to a simple interface.


Why Streamlit?

There are plenty of frontend frameworks out there, but here’s why Streamlit was perfect for my project:

Streamlit gave me the option to build a nice looking frontend, without the hassle of going through HTML, CSS, JS, or another tool. I could focus on Python 🐍, which is more of my skill set.

Making API Calls from Streamlit

With the backend already being deployed on Fly.io, I just needed to write some client-side logic in the Streamlit app to call it.

I used the requests library to send GET and POST requests. Below is a simplified example of how to connect the Streamlit saved books input to my FastAPI /user-books/ endpoint:

import requests
import streamlit as st

API_URL = "https://your-fastapi-backend.com"
SAVED_BOOKS_URL = f"{API_URL}/user-books/"

st.title("📚 My Saved Books")

# Assume user is already authenticated and token stored in session
access_token = st.session_state.get("access_token")
user_id = st.session_state.get("user_id")

if not access_token or not user_id:
    st.error("Please log in to view saved books.")
    st.stop()

headers = {"Authorization": f"Bearer {access_token}"}
response = requests.get(SAVED_BOOKS_URL, params={"user_id": user_id}, headers=headers)

if response.status_code != 200:
    st.error("Failed to load saved books.")
    st.stop()

saved_books = response.json()

for book in saved_books:
    st.subheader(book["title"])
    st.write(f"**Author(s):** {book['authors']}")
    st.write(f"**Published:** {book.get('published_date', 'N/A')}")
    st.markdown("---")

This basic example shows how little code is needed to connect and display the data. With that knowledge, it was easy to connect the rest of the FastAPI endpoints to have the app fully functioning.


Handling Environment Variables and CORS

Connecting the frontend and backend isn’t just about sending these API calls. It also requires some behind the scenes work with environment variables and CORS settings to make sure it all runs smoothly.

Environment Variable with a Config Module

Instead of hardcoding environment variables or scattering them around my codebase, I built a dedicated config.py module using Pydantic Settings.

This lets me load configuration from my .env file during local development, use os.environ in Fly.io, and access st.secrets in Streamlit Cloud.

Here is a simplified example that shows the basics of what I did:

from pydantic_settings import BaseSettings
import streamlit as st
import os

class Settings(BaseSettings):
    API_URL: str
    # other secrets...

    class Config:
        env_file = ".env"

def load_settings():
    if "database" in st.secrets:  # running in Streamlit Cloud
        return Settings(API_URL=st.secrets["api"]["API_URL"])
    return Settings()  # fallback to env vars for local/dev/deploy

settings = load_settings()

This made it easy to access settings across the app with settings.API_URL, whether running locally, on Fly.io, or on Streamlit Cloud.

I just had to:

CORS Configuration in FastAPI

Since my frontend and backend live on different domains, I ran into a few problems where the browser would block requests. This is where I learned about CORS, or Cross-Origin Resource Sharing. 

In my main.py file, I used CORSMiddleware to explicitly allow requests from the Streamlit frontend. The code looks like this:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://your-streamlit-app.streamlit.app"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

This simple addition ensured that the frontend could securely access protected API endpoints (like /user-books/).

Without this, the browser will block requests made from your frontend to a different domain (like your backend API), resulting in errors like CORS policy blocked this request.


Deploying the Streamlit Frontend

Once the frontend was ready, I deployed it using Streamlit Cloud. This is a great service as it’s free and is an easy way to deploy the frontend.

I just followed this simple process:

  1. Push my code to GitHub
  2. Link the repo in Streamlit Cloud
  3. Add my environment variables to the Streamlits Secrets in the app’s setting on Streamlit Cloud
  4. Click deploy

That was it!

In just a few minutes my full-stack app was live and usable on the web. It was entirely built in Python using FastAPI and Streamlit.


Final Thoughts

This project was a highlight of my Pybites PDM experience. 🎉

It showed me how powerful it is to build full-stack apps entirely in Python. 🚀

FastAPI gave me a modern, efficient backend, and Streamlit let me ship a clean, interactive frontend in record time.

Another key takeaway was how Docker simplified everything — from local development to deployment on Fly.io.

With containerization, my backend ran the same way everywhere, giving me confidence and consistency.

If you’re a Python dev looking to go full-stack without diving into JavaScript, this approach is perfect.

And if you add Docker into the mix, you’ll be learning modern development workflows that scale well for future projects.

April 29, 2025 10:26 AM UTC


Zato Blog

Sustainable water management with IoT, Open Source and Python

Sustainable water management with IoT, Open Source and Python

"Towards a cyber-physical system for sustainable and smart building: a use case for optimizing water consumption on a SmartCampus" is an interesting, Open Access paper via Springer Publishing authored by Sergio Barroso Ramírez, Pablo Bustos and Pedro Núñez Trujillo of Universidad de Extremadura uses Python, Zato and other open source technologies for IoT integrations.

More resources

➤ Python API integration tutorials
What is an integration platform?
Python Integration platform as a Service (iPaaS)
What is an Enterprise Service Bus (ESB)? What is SOA?
Open-source iPaaS in Pytho

April 29, 2025 08:00 AM UTC


Tryton News

Tryton Release 7.6

We are proud to announce the 7.6 release of Tryton.
This release provides many bug fixes, performance improvements and some fine tuning.
You can give it a try on the demo server, use the docker image or download it here.
As usual upgrading from previous series is fully supported.

Here is a list of the most noticeable changes:

Changes for the User

Client

We added a new menu entry on the list to reset the column widths to their original size.

A new widget has been added on the form to pick a color.

A simple chat widget has been added on the sidebar in beta.
It allows to chat in live between users on specific document.

When saving a CSV export, the option to ignore the search limit is now stored also on the server.

Web

It is now possible to resize the column widths (like on the desktop client).

Accounting

As creating payment term is not always easy, we provide now by default the most common payment terms like “Net 30 days”, “Net 30 days End of Month” etc.

The legacy numbering of account move based on journal has been removed and the Post Number field has been renamed to Number.
Also the numbering of account move is using now a strict sequence to guarantee any missing number.

The sign of the amount of account and analytic budgets has been inverted to be the same as the income statements.

The Spanish AEAT reports are not using a start and end periods instead of a list which may not be continuous.

Depending on the payment method, it is no more always needed to group payments to process them. For example, Stripe and Braintree payments do not create a payment group as they are managed individually.

It is now possible to download all the SEPA message at once.

When entering manually, the statement is using its start date to compute the start balance instead of balance of the last statement. This is useful if for some reasons, you are not entering the statements in the chronological order.

We added a new report displaying each statement journal with its latest date and balance.

It is now possible to order by custom preferences the bank account of a party. This is usefull for defining payment bank account as Tryton always pick the first one by default.

Sale

We manage now the expiration of quotation. A validity duration can be configured for the quotations. It is not possible to confirm a quotation with a sale date after the expiration date. When a quotation reaches its expiration without being confirmed, it will be automatically cancelled.

It is now possible to open a list of all the products from a sale order. This is useful for example to verify the available quantities at once.

A new action has been added to the complaint to create automatically a coupon number from a promotion. It is a common practice to answer to customer complaint by giving them such coupon. This feature prevents to give the access right to create coupon to the user managing the complaints.

The actual quantity of the sale line is now used when processing a complaint instead of the ordered quantity.

We added on the sale the original amount and total before any promotion was applied. This is useful if you want to show those amount to the customer.

We added a menu entry to open all the coupon numbers of promotion. This eases the management when you have a lot of coupon numbers.

Now if a secondary unit is defined on the product customer, then it is filled automatically on the sale line.

Purchase

A date has been added to the purchase orders to store when the quotation will expire. This improves the following of quotations before they expire.

Now if a secondary unit is defined on the product supplier, then it is filled automatically on the purchase line.

Stock

We display the quantity of product when searching for a location; product or lot from a stock move.

The internal shipment gains a packed state when it is between two warehouses. This way we support also to create packages and shipping labels for such shipment.
We also compute the measurements for the internal shipments as needed for some carrier.

The location place is now displayed on inventory line to help finding the products in the location.

Company

It is now possible to define which tax identifier to use for a company per country and/or organization. For example a Belgian company with a Belgian VAT may have a French VAT number when doing business in France.

Country

Tryton can now search subdivision by their shorted code. This provides a better user experience as the full subdivision codes are often prefixed with the country code that the user usually do no type.

Incoterm

We do not require anymore the Incoterm for sale between European countries.

Notification

It is now allowed to set a fallback user on notification without a field set. So the user is always notified.

Party

We support now to enter structured address (with entries for the street name, building number, post box etc), then such address will be formatted following its country. We include in standard the format of 222 countries.

We notify the user if he is about to create a contact mechanism that already exists.

We added new tax identifiers such as the Brazilian Company Identifier, the Canadian Business Number and the Croatian Identification Number.

Web Shop

On Shopify, we archive products that are removed from the shop instead of deleting them. This way if they are added back, we do not loose any customization done in Shopify admin.
In the same way, we do not update the product description if it is empty so they can be fully managed in Shopify admin.

Changes for the System Administrator

Web User

We allow users of the *Party Administrator" group to edit web users.

Changes for the Developer

Server

To follow the official supported Python version, the support of Python 3.13 has been added and support of Python 3.8 has been removed.

We lock now records at the transaction start like for the table.

We include a DecimalNull in the tools that behaves like the SQL NULL by with Decimal.

We introduce a chat backend as beta.

Transaction.check_warnings is now a dictionary used to delete warnings all at once at the end of the transaction.

The target model of a field is now stored in ir.model.field.

The ModelStorage has been reworked to add dedicate hooks that replace and simplify in many cases the need to extend the create, write and delete methods. There is now:

And there is now a ModelStorage.compute_fields method used to store new values for computed fields.
All those changes allowed to remove almost all the extension of the CWD methods reducing the complexity and improve the performance.

The select timeout for the cache channels is now configurable.

The series of trytond is now stored in the database. This allows the server to prevent to use by mistake a database from a different series.

The wizard state views are now filled with the default values. This removes the need to use getattr with a default value.

As passlib does not support Python 3.13, it has been replaced by pwdlib.

It is now possible to limit the size of the RPC arguments. All the standard RPC methods have been reviewed to add such limitation when it makes sense.

The field_names argument is now optional when calling Model.default_get.

The XML data are now always synchronized with the database. So the ModelStorage.check_xml_record method has been removed.

When searching on Char and Text, the None values are converted as empty string which is the expected behavior by users.

It is now allowed to return instances as default values instead of id. The server will convert them automatically into ids for the client.

The metadata columns are now added automatically to the query of the ModelSQL.table_query.

The Report.header_key receive now also the data on which the report is executed. This permits to group records based on data from a wizard for example.

The integer part of the digits attribute is now also validated by the server.

We added the support for DATE_TRUNC and EXTRACT from INTERVAL to the SQLite backend.

Tryton now supports up to 4 translations depending on the plural setup of the language.

The methods msg_gettext and msg_ngettext are now available in the report context.

Tryton set the Decimal precision of the default context from the TRYTOND_DECIMAL_PREC environment variable.

We replaced the use of docstring of Model by a __string__ attribute filled with a default value based on the __name__ value. This allow to run the server with the level 2 of optimization of Python.
The name field of ir.model has been renamed into string and model into name and the field_description of ir.model.field into string.

Proteus

We added an helper function launch_action which allow to launch an action on a lits of records using its XML ID. This ease writing scenario to test those actions.

Client

A new color widget type has been added for the icon and image fields.

Accounting

A company argument has been added to method to check the credit limit to be explicit for which company is the amount it checked instead of relying on the contextual value.

The description field on payment has been renamed into reference to follow the Tryton naming convention and reflect the actual usage.

The Stripe checkout is now using the Payment Element.

Party

The name field of address has been rename to building_name.

The co_rut tax identifier has been replaced by co_nit.

Sale

We have added a scheduled task to confirm sales based on payment. This is to strengthen the process in case the payment is succeeded and the sale can not yet be confirmed.

We removed the name field on the promotion coupon.

2 posts - 1 participant

Read full topic

April 29, 2025 06:00 AM UTC

April 28, 2025


Mike Driscoll

Creating TUI Applications with Textual and Python Kickstarter Launched

Text-based user interfaces (TUIs) are making a BIG comeback. Many developers of today need to easy-to-use applications to configure and run jobs on headless servers. You can make your own life and the lives of your team so much easier by learning how to create a TUI yourself.

Textual is a rapid application development framework for your terminal or web browser that is written in Python. You can build complex, sophisticated applications in your terminal. While terminal applications are text-based rather than pixel-based, they still provide fantastic user interfaces.

Back the Kickstarter Now!

 

CSV Viewer TUI

The Textual package allows you to create widgets in your terminal that mimic those used in a web or GUI application.

Creating TUI Applications with Textual and Python is to teach you how to use Textual to make striking applications of your own. The book’s first half will teach you everything you need to know to develop a terminal application.

The book’s second half has many small applications you will learn how to create. Each chapter also includes challenges to complete to help cement what you learn or give you ideas for continued learning.

Here are some of the applications you will create:

Calculator

CSV Viewer

CSV Viewer TUI

MP3 Player

MP3 Player TUI

Weather Application

Weather TUI

Text Editor

Text Editor TUI

What You’ll Learn

In this book, you will learn about the following:

Rewards to Choose From

As a backer of this Kickstarter, you have some choices to make. You can receive one or more of the following, depending on which level you choose when backing the project:

Writing Style

This book will be written in my conversational style. Creating TUI Applications with Textual and Python is over 400 pages long and will teach you all you need to know to be able to use the Textual package effectively.

If you’ve never read anything of mine before, you can download the original Python 101 for free or read it online. You can also check out this blog for writing examples.

About the Author

My name is Michael Driscoll, and I am a professional full-time Python programmer by day and Python blogger by night. I have been programming almost exclusively in Python for almost 15 years. I am also a contributor to Real Python. My previous successful campaigns include these other books:

I also have two books that are published by Apress and Packt Publishing:

Book formats

The finished book will be made available in the following formats:

The paperback is a 8.5″ x 11″ book and approximately 450+ pages in length.

Back the Kickstarter Today!

The post Creating TUI Applications with Textual and Python Kickstarter Launched appeared first on Mouse Vs Python.

April 28, 2025 02:01 PM UTC


Real Python

Managing Python Projects With uv: An All-in-One Solution

The uv tool is a high-speed package and project manager for Python. It’s written in Rust and designed to streamline your workflow. It offers fast dependency installation and integrates various functionalities into a single tool.

With uv, you can install and manage multiple Python versions, create virtual environments, efficiently handle project dependencies, reproduce working environments, and even build and publish a project. These capabilities make uv an all-in-one tool for Python project management.

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

  • uv is a Python package and project manager that integrates multiple functionalities into one tool, offering a comprehensive solution for managing Python projects.
  • uv is used for fast dependency installation, virtual environment management, Python version management, and project initialization, enhancing productivity and efficiency.
  • uv can build and publish Python packages to package repositories like PyPI, supporting a streamlined process from development to distribution.
  • uv automatically handles virtual environments, creating and managing them as needed to ensure clean and isolated project dependencies.

To dive deeper into managing your Python projects efficiently with uv, you should have a basic understanding of using Python virtual environments, setting up pyproject.toml files for projects, and building distributable packages for a project.

Get Your Code: Click here to download the free sample code you’ll use to learn about managing Python projects with uv.

Take the Quiz: Test your knowledge with our interactive “Managing Python Projects With uv: An All-in-One Solution” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Managing Python Projects With uv: An All-in-One Solution

In this quiz, you'll test your understanding of the uv tool, a high-speed package and project manager for Python.

Getting to Know uv for Python

Recently, a few exciting tools built with the Rust programming language have appeared in the Python tooling ecosystem. Ruff—a linter and code formatter for Python—is a well-known and popular example of one of these tools.

In this tutorial, you’ll explore another cool tool made with Rust for Python. You’ll get to know uv, an extremely fast Python package and project manager.

The main idea behind these tools is to accelerate your Python workflow by speeding up your project management actions. For example, Ruff is 10 to 100 times faster than linters like Flake8 and code formatters like Black. Similarly, for package installation, uv is 10 to 100 times faster than pip.

Additionally, uv integrates into one tool most of the functionality provided by tools like pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. Therefore, uv is an all-in-one solution.

Here’s a quick list of key uv features for managing your Python projects:

  • Fast dependency installation: Installs dependencies really fast, which is especially useful for large dependency trees.
  • Virtual environment management: Automatically creates and manages virtual environments.
  • Python version management: Allows the installation and management of multiple Python versions.
  • Project initialization: Scaffolds a full Python project, including the root directory, Git repository, virtual environment, pyproject.toml, README, and more.
  • Dependency management: Installs, updates, removes, and locks direct and transitive dependencies, which allows for environment reproducibility.
  • Package builds and publication management: Allows you to build and publish packages to package repositories like the Python Package Index (PyPI).
  • Developer tooling support: Installs and lets you run development tools, such as pytest, Black, and Ruff.

Apart from these features, uv is a standalone binary that allows for a smooth installation and quick upgrades. You don’t need to have Python installed on your system to install uv.

So, with this quick summary of uv and its main features, you’re ready to install this tool on your system. That’s what you’ll do in the following section. Additionally, you’ll learn how to update your uv installation.

Installing uv to Manage Python Projects

The first step in using any tool is to install it on your operating system. To install uv, you have several options. The quickest one would be to use the standalone installer. Another friendly option is to install uv from PyPI using other tools like pipx or pip.

In the official uv installation guide, you’ll find several other installation options. For example, you can use tools like Homebrew and Cargo, depending on your current platform and operating system. However, in this tutorial, you’ll only explore the standalone installer and the PyPI options.

Using the Standalone Installer

The uv project provides a standalone installer that you can use to download and install the tool in your system. Below are the relevant commands for the three main operating systems:

Windows PowerShell
PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Copied!
Shell
$ curl -LsSf https://astral.sh/uv/install.sh | sh
Copied!

If you don’t have curl installed on your system, then you can use wget as shown below:

Shell
$ wget -qO- https://astral.sh/uv/install.sh | sh
Copied!

These commands will download and install the latest binary of uv in your system. If you’d like to install a specific version of the tool instead of the latest, then you can add the version number to the download URL right after the uv/ part:

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


[ 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 ]

April 28, 2025 02:00 PM UTC

Quiz: Managing Python Projects With uv: An All-in-One Solution

By working through this quiz, you’ll revisit how Python’s uv integrates multiple functionalities into one tool, offering a comprehensive solution for managing Python projects.

You can use it for fast dependency installation, virtual environment management, Python version management, and project initialization, enhancing your productivity and efficiency.


[ 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 ]

April 28, 2025 12:00 PM UTC


Talk Python to Me

#503: The PyArrow Revolution

Pandas is at a the core of virtually all data science done in Python, that is virtually all data science. Since it's beginning, Pandas has been based upon numpy. But changes are afoot to update those internals and you can now optionally use PyArrow. PyArrow comes with a ton of benefits including it's columnar format which makes answering analytical questions faster, support for a range of high performance file formats, inter-machine data streaming, faster file IO and more. Reuven Lerner is here to give us the low-down on the PyArrow revolution.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/nordlayer'>NordLayer</a><br> <a href='https://talkpython.fm/auth0'>Auth0</a><br> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br/> <br/> <h2 class="links-heading">Links from the show</h2> <div><strong>Reuven</strong>: <a href="https://github.com/reuven?featured_on=talkpython" target="_blank" >github.com/reuven</a><br/> <strong>Apache Arrow</strong>: <a href="https://github.com/apache/arrow?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Parquet</strong>: <a href="https://parquet.apache.org/?featured_on=talkpython" target="_blank" >parquet.apache.org</a><br/> <strong>Feather format</strong>: <a href="https://arrow.apache.org/docs/python/feather.html?featured_on=talkpython" target="_blank" >arrow.apache.org</a><br/> <strong>Python Workout Book (45% off with code talkpython45)</strong>: <a href="https://mng.bz/nZNv?featured_on=talkpython" target="_blank" >manning.com</a><br/> <strong>Pandas Workout Book (45% off with code talkpython45)</strong>: <a href="https://mng.bz/Qwvm?featured_on=talkpython" target="_blank" >manning.com</a><br/> <strong>Pandas</strong>: <a href="https://pandas.pydata.org/?featured_on=talkpython" target="_blank" >pandas.pydata.org</a><br/> <strong>PyArrow CSV docs</strong>: <a href="https://arrow.apache.org/docs/python/csv.html?featured_on=talkpython" target="_blank" >arrow.apache.org</a><br/> <strong>Future string inference in Pandas</strong>: <a href="https://pandas.pydata.org/docs?featured_on=talkpython" target="_blank" >pandas.pydata.org</a><br/> <strong>Pandas NA/nullable dtypes</strong>: <a href="https://pandas.pydata.org/docs/user_guide/integer_na.html?featured_on=talkpython" target="_blank" >pandas.pydata.org</a><br/> <strong>Pandas `.iloc` indexing</strong>: <a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.iloc.html?featured_on=talkpython" target="_blank" >pandas.pydata.org</a><br/> <strong>DuckDB</strong>: <a href="https://duckdb.org?featured_on=talkpython" target="_blank" >duckdb.org</a><br/> <strong>Pandas user guide</strong>: <a href="https://pandas.pydata.org/docs/user_guide/?featured_on=talkpython" target="_blank" >pandas.pydata.org</a><br/> <strong>Pandas GitHub issues</strong>: <a href="https://github.com/pandas-dev/pandas/issues?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=IHd-bgeHrv0" target="_blank" >youtube.com</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/503/the-pyarrow-revolution" target="_blank" >talkpython.fm</a><br/> <br/> <strong>--- Stay in touch with us ---</strong><br/> <strong>Subscribe to Talk Python on YouTube</strong>: <a href="https://talkpython.fm/youtube" target="_blank" >youtube.com</a><br/> <strong>Talk Python on Bluesky</strong>: <a href="https://bsky.app/profile/talkpython.fm" target="_blank" >@talkpython.fm at bsky.app</a><br/> <strong>Talk Python on Mastodon</strong>: <a href="https://fosstodon.org/web/@talkpython" target="_blank" ><i class="fa-brands fa-mastodon"></i>talkpython</a><br/> <strong>Michael on Bluesky</strong>: <a href="https://bsky.app/profile/mkennedy.codes?featured_on=talkpython" target="_blank" >@mkennedy.codes at bsky.app</a><br/> <strong>Michael on Mastodon</strong>: <a href="https://fosstodon.org/web/@mkennedy" target="_blank" ><i class="fa-brands fa-mastodon"></i>mkennedy</a><br/></div>

April 28, 2025 08:00 AM UTC


Python Bytes

#430 Or you go to jail

<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://ichard26.github.io/blog/2025/04/whats-new-in-pip-25.1/?featured_on=pythonbytes">pip 25.1 has dependency groups, pylock.toml, plus more</a></strong></li> <li><strong><a href="https://bsky.app/profile/aiohttp.org/post/3lmyhz6uhks2u?featured_on=pythonbytes">aiohttp goes free threaded</a></strong></li> <li><strong><a href="https://github.com/astral-sh/uv/releases/tag/0.6.15?featured_on=pythonbytes">uv 0.6.15 supports pylock.toml</a></strong></li> <li><a href="https://github.com/ariebovenberg/whenever?featured_on=pythonbytes"><strong>Whenever</strong></a></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=BGhDge-iUTw' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="430">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by <a href="https://pythonbytes.fm/porkbun"><strong>Porkbun</strong></a>! Use our link <a href="https://pythonbytes.fm/porkbun"><strong>pythonbytes.fm/porkbun</strong></a> and get a .app or .dev domain for $5.99 at Porkbun.</p> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy"><strong>@mkennedy@fosstodon.org</strong></a> <strong>/</strong> <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes"><strong>@mkennedy.codes</strong></a> <strong>(bsky)</strong></li> <li>Brian: <a href="https://fosstodon.org/@brianokken"><strong>@brianokken@fosstodon.org</strong></a> <strong>/</strong> <a href="https://bsky.app/profile/brianokken.bsky.social?featured_on=pythonbytes"><strong>@brianokken.bsky.social</strong></a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes"><strong>@pythonbytes@fosstodon.org</strong></a> <strong>/</strong> <a href="https://bsky.app/profile/pythonbytes.fm"><strong>@pythonbytes.fm</strong></a> <strong>(bsky)</strong></li> </ul> <p>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually <strong>Monday</strong> at 10am PT. Older video versions available there too.</p> <p>Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it. </p> <p><strong>Brian #1:</strong> <a href="https://ichard26.github.io/blog/2025/04/whats-new-in-pip-25.1/?featured_on=pythonbytes">pip 25.1 has dependency groups, pylock.toml, plus more</a></p> <ul> <li>post <a href="https://ichard26.github.io/blog/2025/04/whats-new-in-pip-25.1/?utm_source=pocket_shared&featured_on=pythonbytes">What's new in pip 25.1 - Dependency groups!</a></li> <li>Richard Si</li> <li>Discovered this through <a href="https://bsky.app/profile/hugovk.dev/post/3lnqd2fosq224?featured_on=pythonbytes">Hugo van Kemenade</a></li> <li><p>Dependency groups, PEP 735, supported</p> <pre><code># pyproject.toml [dependency-groups] test = ["pytest", "pytest-xdist"] lint = ["mypy", "isort"] # Dependency Groups can include other groups! ✨ dev = [ {include-group = "test"}, {include-group = "lint"} ] </code></pre></li> <li><p>Package installation progress bar</p></li> <li>Resumable downloads</li> <li>Experimental lockfile generation, PEP 751, with pip lock <ul> <li>so cool</li> </ul></li> <li>pip index versions is stable, no longer experimental <ul> <li>use this to get a list of available versions</li> <li>ex: python3 -m pip index versions pytest-check</li> <li>combine with --json to get a nice script readable output</li> </ul></li> </ul> <p><strong>Michael #2:</strong> <a href="https://bsky.app/profile/aiohttp.org/post/3lmyhz6uhks2u?featured_on=pythonbytes">aiohttp goes free threaded</a></p> <ul> <li>Thanks to months of consistent contributions by Lysandros Nikolaou, all of the mandatory dependencies of <a href="https://www.dropbox.com/?q=%23aiohttp&featured_on=pythonbytes">#aiohttp</a> now ship free-threaded variants of <a href="https://www.dropbox.com/?q=%23wheels&featured_on=pythonbytes">#wheels</a>!</li> <li>This unlocks the same in <a href="https://github.com/aio-libs/aiohttp?featured_on=pythonbytes">aiohttp</a>!</li> </ul> <p><strong>Brian #3:</strong> <a href="https://github.com/astral-sh/uv/releases/tag/0.6.15?featured_on=pythonbytes">uv 0.6.15 supports pylock.toml</a></p> <ul> <li>Discovered through <a href="https://bsky.app/profile/snarky.ca/post/3lngwrbkbm22g?featured_on=pythonbytes">Brett Cannon</a></li> <li>So far, these projects support pylock.toml <ul> <li>pip</li> <li>pip-audit</li> <li>pdm</li> <li>uv</li> </ul></li> <li>With uv <ul> <li>To export a uv.lock to the pylock.toml format, <ul> <li>run: uv export -o pylock.toml</li> </ul></li> <li>To generate a pylock.toml file from a set of requirements, <ul> <li>run: uv pip compile -o pylock.toml -r requirements.in</li> </ul></li> <li>To install from a pylock.toml file, <ul> <li>run: uv pip sync pylock.toml or uv pip install -r pylock.toml</li> </ul></li> </ul></li> </ul> <p><strong>Michael #4:</strong> <a href="https://github.com/ariebovenberg/whenever?featured_on=pythonbytes"><strong>Whenever</strong></a></p> <ul> <li>via Pat Decker</li> <li>Typed and DST-safe datetimes for Python, available in Rust or pure Python.</li> <li><em>Whenever</em> helps you write <strong>correct</strong> and <strong>type checked</strong> datetime code.</li> <li>It's also <strong>way faster</strong> than other third-party libraries—and usually the standard library as well.</li> </ul> <p><strong>Extras</strong> </p> <p>Brian:</p> <ul> <li><a href="https://everyuuid.com?featured_on=pythonbytes">Every UUID</a> </li> </ul> <p>Michael:</p> <ul> <li><a href="https://www.pillar.security/blog/new-vulnerability-in-github-copilot-and-cursor-how-hackers-can-weaponize-code-agents?featured_on=pythonbytes">New Vulnerability in GitHub Copilot and Cursor: How Hackers Can Weaponize Code Agents</a> via Brian Skinn</li> <li>And <a href="https://www.darkreading.com/application-security/ai-code-tools-widely-hallucinate-packages?featured_on=pythonbytes">typosquatting in the AI age</a></li> <li>Firefox Send alternatives <ul> <li><a href="https://github.com/kern/filepizza?featured_on=pythonbytes">file.pizza</a> via <a href="https://social.tchncs.de/@rafaelwo/114393487740735715?featured_on=pythonbytes">@rafaelwo</a> </li> <li><a href="https://bitwarden.com/products/send/?featured_on=pythonbytes">bitwarden send</a></li> </ul></li> </ul> <p><strong>Joke:</strong> <strong>Can you Vibe?</strong></p> <ul> <li><a href="https://www.youtube.com/watch?v=JeNS1ZNHQs8"><strong>Interview with Vibe Coder in 2025</strong></a></li> <li><a href="https://www.youtube.com/watch?v=_2C2CNmK7dQ"><strong>Senior Engineer tries Vibe Coding</strong></a></li> </ul>

April 28, 2025 08:00 AM UTC


Python GUIs

Kivy's Complex Widgets — Learn How to Use Kivy's Complex UX Widgets in Your Apps

Kivy is a powerful framework for developing multi-touch GUI applications using Python. It provides a set of rich built-in widgets which you can use to build complex GUI applications.

In a previous tutorial we covered the basic Kivy widgets such as text inputs, buttons and checkboxes. In this tutorial, we will take things further, exploring some more of the more complex widgets that Kivy provides. These include: Bubble, DropDown, FileChooser, Popup, Spinner, RecycleView, TabbedPanel, VideoPlayer, and VKeyboard. With them, you can add advanced features to your Kivy apps.

Writing an Outline Kivy App

We'll start this tutorial with a simple application skeleton, which we will then modify below. Save the following code in a file named app.py:

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout

class WidgetNameApp(App):
    title = "WidgetName Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout()

        return root

WidgetNameApp().run()

Here, we've created a Kivy application with an empty window. The BoxLayout acts as the root widget, this will act as the container to add our complex widgets to. The build() method sets the window's background color to a dark teal shade and adjusts the window size to 360x640 pixels, which is a mobile-friendly size.

To learn more about creating your first Kivy app, check out the Getting Started With Kivy for GUI Development tutorial.

Providing Option Selections With Spinner

The Spinner is a dropdown selector that allows users to choose one option from multiple choices. This is ideal when working with a list of simple text choices. Below is an example that builds a Spinner that lets you select from different parts of this website.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner

class SpinnerApp(App):
    title = "Spinner Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (300, 300)

        root = FloatLayout()

        # Create the Spinner
        spinner = Spinner(
            text="Home",
            values=("Home", "Latest", "FAQ", "Forum", "Contact", "About"),
            size_hint=(None, None),
            size=(200, 70),
            pos_hint={"center_x": 0.2, "center_y": 0.9},
            sync_height=True,
        )

        root.add_widget(spinner)

        return root

SpinnerApp().run()

The Spinner widget works as a simple dropdown list, allowing users to select one option from multiple text choices. We've set the Spinner to start with "Home" as the default text and provided other options ("Latest", "FAQ", "Forum", "Contact", and "About") as a list of values.

You need to repeat the "Home" option in values so that you don't lose it when you select another option.

Run it! You'll get an app that looks as shown below.

A Kivy app showing a Spinner Widget A Kivy app showing a Spinner Widget

The dropdown spinner allows users to select from predefined choices. You can use this widget to create elements that work like dropdown list, optimizing space and providing a clean UI.

Providing Options With DropDown List

The DropDown widget provides a more complex menu component that allows users to choose from multiple options. Like spinner, this provides an intuitive way for users to select from a set of choices, but here you can display more than just text. This makes it more complex to use, but allows for more flexibility.

Below is an example of using the widget to create a dropdown list that lets you select your favorite GUI library, displayed on a series of Button objects.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown

class DropDownApp(App):
    title = "DropDown Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (200, 200)

        root = BoxLayout(orientation="vertical", padding=10, spacing=10)

        # Create a dropdown with 4 buttons
        dropdown = DropDown()
        for item in ["Kivy", "PyQt6", "PySide6", "Tkinter"]:
            option_btn = Button(text=item, size_hint_y=None, height=50, width=150)
            option_btn.bind(on_release=lambda btn: dropdown.select(btn.text))
            dropdown.add_widget(option_btn)

        # Create a main button to show the dropdown
        button = Button(
            text="Library",
            size_hint=(None, None),
            size=(150, 50),
        )
        button.bind(on_release=dropdown.open)
        dropdown.bind(
            on_select=lambda instance, text: setattr(button, "text", text),
        )
        root.add_widget(button)
        return root

DropDownApp().run()

In this example, we have a DropDown widget that lets the user to select a library from a list of options. You populate the dropdown with four options "Kivy", "PyQt6", "PySide6", and "Tkinter", which are displayed in Button objects.

We set each button to trigger the dropdown.select() method when clicked, passing the button's text as the selected value.

Then, we anchor the dropdown to a Library button. When we press the Library button, the dropdown menu opens, displaying the options. Once we select an option, the on_select event updates the main button's text to reflect the chosen library.

Run it! You'll get a window with a dropdown list in the lower left corner. Click in the dropdown widget to change the current selection.

A Kivy app showing a DropDown widget A Kivy app showing a DropDown widget

Accessing Files With FileChooser*

The filechooser module provides classes for describing, displaying and browsing file systems. In this module there are two ready-made widget views which present the file system as either a list, or as icons. The example below demonstrates these both in action.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.filechooser import FileChooserIconView, FileChooserListView

class FileChooserApp(App):
    title = "FileChooser Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout(orientation="vertical")

        # Create icon-view and list-view file choosers
        filechooser_icons = FileChooserIconView()
        filechooser_list = FileChooserListView()

        root.add_widget(filechooser_icons)
        root.add_widget(filechooser_list)

        return root

FileChooserApp().run()

In this example, we create file chooser widgets to browse and select files using two different views:

  1. Icon view (FileChooserIconView)
  2. List view (FileChooserListView)

The icon view displays files as wrapped rows of icons, clicking on a folder icon will navigate down into that folder. The list view presents them in a list-tree like format, where clicking on a folder will show files and folders nested under it.

Run it! On macOS, you'll see a window that looks something like the following. Try clicking on the folder icons and entries in the list view to see how navigation works in the two examples.

A Kivy app showing file chooser widgets A Kivy app showing file chooser widgets

Building Quick Dialogs With Popup

The Popup widget allows us to display modal dialogs with custom content, layouts and widgets. They can be used to show messages or ask for input. The following popup message displays a simple message, with a title, message and OK button.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup

class PopupApp(App):
    title = "Popup Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (400, 400)

        root = FloatLayout()

        button = Button(
            text="Open Popup",
            on_press=lambda x: self.show_popup(),
            size_hint=(None, None),
            size=(200, 50),
            pos_hint={"center_x": 0.5, "center_y": 0.5},
        )

        root.add_widget(button)

        return root

    def show_popup(self):
        # Create and show the Popup
        popup = Popup(
            title="Info",
            size_hint=(0.6, 0.6),
            size=(300, 300),
            auto_dismiss=False,
        )

        layout = BoxLayout(orientation="vertical", spacing=10, padding=10)

        message = Label(text="Hello, World!")

        ok_button = Button(text="OK", size_hint=(None, None), size=(80, 40))
        ok_button.bind(on_release=popup.dismiss)

        layout.add_widget(message)
        layout.add_widget(ok_button)

        popup.content = layout
        popup.open()

PopupApp().run()

In this example, you create a Popup widget that displays information as a modal dialog. When the user clicks the Open Popup button, the show_popup() method is triggered, creating a Popup that occupies 60% of the screen in both directions.

We set auto_dismiss to False, which means the popup won't close if we click outside of it. The popup contains the Hello, World! message and an OK button. When we click the button, we dismiss (close) the popup. Popups are effective for displaying alerts, confirmations, or other information in a Kivy app.

Run it! You'll get a window with a button labeled "Open Popup". Click on the Open Popup button to display the popup window.

A Kivy app showing a popup dialog A Kivy app showing a popup dialog

Creating Contextual Popups With Bubble

The Bubble widget is a UI element commonly used for contextual popups, tooltips, or chat applications. Below is a quick Kivy application that shows some text and lets you click on it to change its format. We'll start by importing the necessary objects and subclassing the Bubble class:

python
from kivy.app import App
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.uix.bubble import Bubble, BubbleButton, BubbleContent
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label

class FormattingBubble(Bubble):
    def __init__(self, target_text, **kwargs):
        super().__init__(**kwargs)

        # Customizing the bubble
        self.size_hint = (None, None)
        self.size = (dp(120), dp(50))
        self.arrow_pos = "top_mid"
        self.orientation = "horizontal"
        self.target_label = target_text

        # Add formatting buttons
        bold_btn = BubbleButton(text="Bold")
        italic_btn = BubbleButton(text="Italic")
        bold_btn.bind(on_release=lambda x: self.on_format("bold"))
        italic_btn.bind(on_release=lambda x: self.on_format("italic"))

        # Add the buttons to the bubble
        bubble_content = BubbleContent()
        bubble_content.add_widget(bold_btn)
        bubble_content.add_widget(italic_btn)

        self.add_widget(bubble_content)

    def on_format(self, format_type):
        if format_type == "bold":
            self.target_label.text = f"[b]{self.target_label.text}[/b]"
        elif format_type == "italic":
            self.target_label.text = f"[i]{self.target_label.text}[/i]"
        self.parent.remove_widget(self)

class BubbleApp(App):
    title = "Bubble Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = FloatLayout()

        self.text = Label(
            text="Click this text to apply formatting",
            size_hint=(0.8, 0.2),
            pos_hint={"center_x": 0.5, "center_y": 0.5},
            markup=True,
        )
        self.text.bind(on_touch_down=self.show_bubble)

        root.add_widget(self.text)
        root.bind(on_touch_down=self.dismiss_bubbles)

        return root

    def show_bubble(self, instance, touch):
        if instance.collide_point(*touch.pos):
            self.remove_all_bubbles()
            bubble = FormattingBubble(target_text=self.text)
            bubble.pos = (
                touch.x - bubble.width / 2, touch.y - bubble.height - dp(10)
            )
            self.root.add_widget(bubble)

    def dismiss_bubbles(self, instance, touch):
        if instance == self.root and not self.text.collide_point(*touch.pos):
            self.remove_all_bubbles()

    def remove_all_bubbles(self):
        for widget in self.root.children[:]:
            if isinstance(widget, FormattingBubble):
                self.root.remove_widget(widget)
                return

BubbleApp().run()

The FormattingBubble class inherits from Bubble and provides text formatting options for a label. It initializes with a specific size, arrow position, and horizontal layout. The bubble will contain two buttons: Bold and Italic. When pressed, these buttons apply the respective formatting to the target text by triggering the on_format() method. This method wraps the text in Kivy's markup tags [b]...[/b] for bold and [i]...[/i] for italic.

The BubbleApp class represents the Kivy application. It sets up a FloatLayout with a centered Label displaying a message. When the user taps the label, the show_bubble() method creates and positions a FormattingBubble above the tapped location.

The app also ensures that only one bubble is visible at a time by removing existing ones before showing a new one. Additionally, tapping outside the label dismisses any active bubbles using the dismiss_bubbles() method.

Run it! The app features a dark teal background and a mobile-friendly window size. The Bubble widget appears when we click the text.

A Kivy app showing a Bubble widget A Kivy app showing a Bubble widget

Displaying Data With RecycleView

The RecycleView widget efficiently displays data by recycling views or graphical elements. Instead of creating a widget for every item in the dataset, RecycleView reuses a small number of widgets to display visible items only, improving performance.

To illustrate, let's create a view that lets you inspect a database of employee profiles. The data is stored in a CSV file that looks like the following:

csv
name,job,department
John Smith,Developer,IT
Jane Doe,Designer,Graphics
Anne Frank,Artist,Painting
David Lee,Engineer,Civil
Ella Brown,Doctor,Medical
Frank Thomas,Chef,Culinary
Henry Ford,Driver,Transport
Nathan Young,Consultant,Business
Olivia King,Manager,Administration
Peter Wright,Director,Management
Queen Bell,President,Executive
Walter Thompson,Assistant,Support
Xena Garcia,Associate,Associate
Zack Harris,Consultant,Consulting

You can read and load this data with the csv module. To visualize the data, you can create a view with the RecycleView widget. For the individual views, you can use the Button widget, which will let you display the employee's profile:

python
import csv

from kivy.app import App
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview import RecycleView

class EmployeesView(RecycleView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.employees_data = self._read_from_csv()

        # Load the employees data into the data attribute
        self.data = [
            {
                "text": f"{employee['name']}",
                "on_release": self._create_callback(employee["name"]),
            }
            for employee in self.employees_data
        ]

        layout_manager = RecycleBoxLayout(
            default_size=(None, dp(56)),
            default_size_hint=(1, None),
            size_hint_y=None,
            orientation="vertical",
        )
        layout_manager.bind(minimum_height=layout_manager.setter("height"))

        self.add_widget(layout_manager)
        self.viewclass = "Button"

    def _create_callback(self, name):
        return lambda: self.on_button_click(name)

    def _read_from_csv(self):
        with open("employees.csv", mode="r") as file:
            return [row for row in csv.DictReader(file)]

    def on_button_click(self, name):
        popup = Popup(
            title=f"{name}'s Profile",
            size_hint=(0.8, 0.5),
            size=(300, 300),
            auto_dismiss=False,
        )
        employees_data = [
            employee for employee in self.employees_data if employee["name"] == name
        ]
        profile = "\n".join(
            [f"{key.capitalize()}: {value}" for key, value in employees_data[0].items()]
        )
        layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
        message = Label(text=profile)
        ok_button = Button(text="OK", size_hint=(None, None))
        ok_button.bind(on_release=popup.dismiss)
        layout.add_widget(message)
        layout.add_widget(ok_button)
        popup.content = layout
        popup.open()

class RecycleViewApp(App):
    title = "RecycleView Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        return EmployeesView()

RecycleViewApp().run()

In this example, we subclass RecycleView to display the list of employees loaded from a CSV file. The _read_from_csv() method opens the file and reads the data using the csv.DictReader() class, which converts each CSV line into a dictionary whose keys come from the file header line.

The data attribute is key for the app to work because it'll hold the data that we want to display. To arrange widgets in a RecycleView, we use a RecycleBoxLayout. The viewclass attribute lets us set the widget that we'll use to display each data item.

It's important to note that for the RecycleView to work properly, we should set viewclass at the end when the data is already loaded and the layout is set up.

Then, we populate the RecycleView view with buttons, each displaying an employee's name. Clicking a button triggers _create_callback(), which generates a callback that opens a popup displaying the selected employee's profile details.

Run it! You'll get a nice-looking window listing the employees. Click a button to view the associated employee's profile. Scroll down to load more profiles.

A Kivy app showing a RecycleView Widget A Kivy app showing a RecycleView Widget

Building Tabbed UIs With TabbedPanel

The TabbedPanel widget lets us organize content into tabs, to improve navigation and optimize the use of space. This is commonly used in settings dialogs where there are lots of options available.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader

class TabbedPanelApp(App):
    title = "TabbedPanel Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        # Create the TabbedPanel
        root = TabbedPanel(do_default_tab=False)

        # Create the tabs
        general_tab = TabbedPanelHeader(text="General")
        general_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        general_content.add_widget(Label(text="General Settings", font_size=40))
        general_tab.content = general_content
        root.add_widget(general_tab)

        editor_tab = TabbedPanelHeader(text="Editor")
        editor_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        editor_content.add_widget(Label(text="Editor Settings", font_size=40))
        editor_tab.content = editor_content
        root.add_widget(editor_tab)

        profile_tab = TabbedPanelHeader(text="Profile")
        profile_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        profile_content.add_widget(Label(text="User Profile", font_size=40))
        profile_tab.content = profile_content
        root.add_widget(profile_tab)

        return root

TabbedPanelApp().run()

In this example, we create a Kivy app that shows a tabbed interface using the TabbedPanel widget. It disables the default tab and manually adds three tabs: General, Editor, and Profile, each represented by a TabbedPanelHeader object.

Inside the tabs, we place a BoxLayout to hold a label that displays a description as a placeholder tab content. Tabs allow us to organize content into visually distinct sections within an application's UI.

Run it! Your app will display three tabs. When you click the tab header, the app shows the tab's content. The active tab shows a light blue line at the bottom.

A Kivy app showing a TabbedPanel Widget A Kivy app showing a TabbedPanel Widget

Try and add some more widgets to each tab panel.

Allowing User Input With VKeyboard

The VKeyboard widget allows you to create a virtual keyboard that is useful for touchscreen applications that require the user to type in text. Below is a short app that demonstrates a virtual keyboard in action. When you type text using the keyboard, it is displayed on the label.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.vkeyboard import VKeyboard

class VKeyboardApp(App):
    title = "VKeyboard Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout(orientation="vertical")

        self.display_label = Label(text="Type in!", font_size=40)
        root.add_widget(self.display_label)

        # Create the virtual keyboard
        keyboard = VKeyboard(size_hint=(1, 0.4))
        keyboard.bind(on_key_up=self.keyboard_on_key_up)
        root.add_widget(keyboard)

        return root

    def keyboard_on_key_up(self, *args):
        keycode = args[1]
        text = args[2]
        if keycode == "backspace":
            if (
                len(self.display_label.text) > 0
                and self.display_label.text != "Type in!"
            ):
                self.display_label.text = self.display_label.text[:-1]
                if self.display_label.text == "":
                    self.display_label.text = "Type in!"
        elif keycode == "spacebar":
            if self.display_label.text == "Type in!":
                self.display_label.text = " "
            else:
                self.display_label.text += " "
        elif keycode in {"enter", "shift", "alt", "ctrl", "escape", "tab", "capslock"}:
            pass
        else:
            if self.display_label.text == "Type in!":
                self.display_label.text = text
            else:
                self.display_label.text += text

VKeyboardApp().run()

In this example, we manually add a virtual keyboard to our app's interface using the VKeyboard widget and display typed text using a label.

When a key is released, the keyboard_on_key_up() method processes the input. Printable characters are appended to the label text. Backspace removes the last character, and the spacebar inserts a space.

You typically wouldn't use the VKeyboard widget as in the example above. Input widgets, like TextInput, will automatically bring up the virtual keyboard when focused on mobile devices.

We ignore special keys like Enter, Shift, Alt, Ctrl, and Escape. This allows us to interact with a virtual keyboard and see the input displayed dynamically in the label.

Run it! A virtual keyboard appears at the button of the app's window, allowing you to enter text in touch-based devices. When you type on the virtual keyboard at the bottom of the app's window, the label reflects what you've typed.

A Kivy app showing a VKeyboard Widget A Kivy app showing a VKeyboard Widget

Conclusion

Kivy provides a rich set of complex UX widgets that you can use to create cross-platform applications. Using the examples above as inspiration you should now be able to use Bubble, DropDown, FileChooser, Popup, Spinner, RecycleView, TabbedPanel, and VKeyboard in your own apps. See if you can extend these examples further, adding more widgets or functionality to them.

April 28, 2025 06:00 AM UTC

April 27, 2025


Real Python

Quiz: How to Manage Python Projects With pyproject.toml

In this quiz, you’ll test your understanding of the pyproject.toml file.

This file is a key component for defining a Python project’s build system, including its requirements and build backend. With appropriate tooling, it can also manage dependencies, optional dependencies, command-line scripts, and dynamic metadata for flexible project configuration.


[ 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 ]

April 27, 2025 12:00 PM UTC